diff --git a/HISTORY.md b/HISTORY.md index 4708f09..3ba4ac0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,10 +4,18 @@ History of SixtyPical 0.20 ---- +* A `point ... into` block no longer initializes the pointer + by default. A subequent `reset` instruction must be used + to initialize the pointer. The pointer may be reset to any + valid offset within the table (not only 0) and it may be + reset multiple times inside the block. +* Local locations need no longer be static. If they are not + static, they are considered uninitialized until assigned, + and they can be declared with an explicit fixed address. * 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. +* Split context off from analyzer and put it in its own module. +* Split the SixtyPical Analysis tests across three files. 0.19 ---- diff --git a/TODO.md b/TODO.md index 86abeb5..97d77a1 100644 --- a/TODO.md +++ b/TODO.md @@ -29,15 +29,7 @@ inner block has finished -- even if there is no `call`.) These holes need to be plugged. -### Reset pointer in `point into` blocks - -We have `point into` blocks, but maybe the action when entering such a -block shouldn't always be to set the given pointer to the start of the given table. - -That is, sometimes we would like to start at some fixed offset. And -sometimes we want to (re)set the pointer, without closing and starting a new block. - -### Pointers associated globally with a table +### Pointers associated globally with a table(?) We have `point into` blocks, but we would also like to sometimes pass a pointer around to different routines, and have them all "know" what table it operates on. @@ -62,16 +54,6 @@ 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 -detect it.) - -This would permit local pointers, which would be one way of addressing the -"same pointer to different tables" problem. - ### Copy byte to/from table Do we want a `copy bytevar, table + x` instruction? We don't currently have one. diff --git a/eg/c64/demo-game/demo-game.60p b/eg/c64/demo-game/demo-game.60p index 3135895..85563a6 100644 --- a/eg/c64/demo-game/demo-game.60p +++ b/eg/c64/demo-game/demo-game.60p @@ -265,6 +265,7 @@ define player_logic logic_routine if c { point ptr into screen { + reset ptr 0 st off, c add ptr, new_pos ld y, 0 @@ -280,6 +281,7 @@ define player_logic logic_routine cmp a, 32 if z { point ptr into screen { + reset ptr 0 st off, c add ptr, pos copy 32, [ptr] + y @@ -288,6 +290,7 @@ define player_logic logic_routine copy new_pos, pos point ptr into screen { + reset ptr 0 st off, c add ptr, pos copy 81, [ptr] + y @@ -307,6 +310,7 @@ define enemy_logic logic_routine if c { point ptr into screen { + reset ptr 0 st off, c add ptr, new_pos ld y, 0 @@ -321,6 +325,7 @@ define enemy_logic logic_routine cmp a, 32 if z { point ptr into screen { + reset ptr 0 st off, c add ptr, pos copy 32, [ptr] + y @@ -329,6 +334,7 @@ define enemy_logic logic_routine copy new_pos, pos point ptr into screen { + reset ptr 0 st off, c add ptr, pos copy 82, [ptr] + y diff --git a/eg/rudiments/buffer.60p b/eg/rudiments/buffer.60p index 72ff18a..18eb6d8 100644 --- a/eg/rudiments/buffer.60p +++ b/eg/rudiments/buffer.60p @@ -11,10 +11,12 @@ define main routine trashes a, z, n, c, ptr { ld y, 0 - copy ^buf, ptr - copy 123, [ptr] + y - copy [ptr] + y, foo - copy foo, [ptr] + y + point ptr into buf { + reset ptr 0 + copy 123, [ptr] + y + copy [ptr] + y, foo + copy foo, [ptr] + y + } // TODO: support saying `cmp foo, 123`, maybe ld a, foo diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 0313f73..4404135 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -1,8 +1,9 @@ # encoding: UTF-8 from sixtypical.ast import ( - Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto + Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto ) +from sixtypical.context import AnalysisContext from sixtypical.model import ( TYPE_BYTE, TYPE_WORD, TableType, PointerType, VectorType, RoutineType, @@ -80,321 +81,6 @@ class IncompatibleConstraintsError(ConstraintsError): pass -class AnalysisContext(object): - """ - A location is touched if it was changed (or even potentially - changed) during this routine, or some routine called by this routine. - - A location is meaningful if it was an input to this routine, - or if it was set to a meaningful value by some operation in this - routine (or some routine called by this routine). - - If a location is meaningful, it has a range. This range represents - the lowest and highest values that it might possibly be (i.e. we know - it cannot possibly be below the lowest or above the highest.) In the - absence of any usage information, the range of a byte, is 0..255 and - the range of a word is 0..65535. - - A location is writeable if it was listed in the outputs and trashes - lists of this routine. A location can also be temporarily marked - unwriteable in certain contexts, such as `for` loops. - """ - def __init__(self, symtab, routine, inputs, outputs, trashes): - self.symtab = symtab - self.routine = routine # Routine (AST node) - self._touched = set() # {LocationRef} - self._range = dict() # LocationRef -> (Int, Int) - self._writeable = set() # {LocationRef} - self._terminated = False - self._gotos_encountered = set() - self._pointer_assoc = dict() - - for ref in inputs: - if self.is_constant(ref): - raise ConstantConstraintError(self.routine, ref.name) - self._range[ref] = self.max_range(ref) - output_names = set() - for ref in outputs: - if self.is_constant(ref): - raise ConstantConstraintError(self.routine, ref.name) - output_names.add(ref.name) - self._writeable.add(ref) - for ref in trashes: - if self.is_constant(ref): - raise ConstantConstraintError(self.routine, ref.name) - if ref.name in output_names: - raise InconsistentConstraintsError(self.routine, ref.name) - self._writeable.add(ref) - - def __str__(self): - return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format( - self.__class__.__name__, - LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable) - ) - - def to_json_data(self): - type_ = self.symtab.fetch_global_type(self.routine.name) - return { - 'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)), - 'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)), - 'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)), - 'touched': ','.join(sorted(loc.name for loc in self._touched)), - 'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()), - 'writeable': ','.join(sorted(loc.name for loc in self._writeable)), - 'terminated': self._terminated, - 'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)), - } - - def clone(self): - c = AnalysisContext(self.symtab, self.routine, [], [], []) - c._touched = set(self._touched) - c._range = dict(self._range) - c._writeable = set(self._writeable) - c._pointer_assoc = dict(self._pointer_assoc) - c._gotos_encountered = set(self._gotos_encountered) - return c - - def update_from(self, other): - """Replaces the information in this context, with the information from the other context. - This is an overwriting action - it does not attempt to merge the contexts. - - We do not replace the gotos_encountered for technical reasons. (In `analyze_if`, - we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the - set of contexts we are updating from, and we want to retain our own.)""" - self.routine = other.routine - self._touched = set(other._touched) - self._range = dict(other._range) - self._writeable = set(other._writeable) - self._terminated = other._terminated - self._pointer_assoc = dict(other._pointer_assoc) - - def each_meaningful(self): - for ref in self._range.keys(): - yield ref - - def each_touched(self): - for ref in self._touched: - yield ref - - def each_writeable(self): - for ref in self._writeable: - yield ref - - def assert_meaningful(self, *refs, **kwargs): - exception_class = kwargs.get('exception_class', UnmeaningfulReadError) - for ref in refs: - 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): - if ref not in self._range: - message = ref.name - if kwargs.get('message'): - message += ' (%s)' % kwargs['message'] - raise exception_class(self.routine, message) - elif isinstance(ref, IndexedRef): - self.assert_meaningful(ref.ref, **kwargs) - self.assert_meaningful(ref.index, **kwargs) - else: - raise NotImplementedError(ref) - - def assert_writeable(self, *refs, **kwargs): - exception_class = kwargs.get('exception_class', ForbiddenWriteError) - for ref in refs: - # locals are always writeable - if self.symtab.has_local(self.routine.name, ref.name): - continue - if ref not in self._writeable: - message = ref.name - if kwargs.get('message'): - message += ' (%s)' % kwargs['message'] - raise exception_class(self.routine, message) - - def assert_in_range(self, inside, outside, offset): - """Given two locations, assert that the first location, offset by the given offset, - is contained 'inside' the second location.""" - assert isinstance(inside, LocationRef) - assert isinstance(outside, LocationRef) - - # inside should always be meaningful - inside_range = self._range[inside] - - # outside might not be meaningful, so default to max range if necessary - if outside in self._range: - outside_range = self._range[outside] - else: - outside_range = self.max_range(outside) - - if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]: - raise RangeExceededError(self.routine, - "Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format( - inside, inside_range, offset, outside, outside_range - ) - ) - - def set_touched(self, *refs): - for ref in refs: - self._touched.add(ref) - # TODO: it might be possible to invalidate the range here - - def set_meaningful(self, *refs): - for ref in refs: - if ref not in self._range: - self._range[ref] = self.max_range(ref) - - def set_top_of_range(self, ref, top): - self.assert_meaningful(ref) - (bottom, _) = self._range[ref] - self._range[ref] = (bottom, top) - - def set_bottom_of_range(self, ref, bottom): - self.assert_meaningful(ref) - (top, _) = self._range[ref] - self._range[ref] = (bottom, top) - - def set_range(self, ref, bottom, top): - self.assert_meaningful(ref) - self._range[ref] = (bottom, top) - - def get_top_of_range(self, ref): - if isinstance(ref, ConstantRef): - return ref.value - self.assert_meaningful(ref) - (_, top) = self._range[ref] - return top - - def get_bottom_of_range(self, ref): - if isinstance(ref, ConstantRef): - return ref.value - self.assert_meaningful(ref) - (bottom, _) = self._range[ref] - return bottom - - def get_range(self, ref): - if isinstance(ref, ConstantRef): - return (ref.value, ref.value) - self.assert_meaningful(ref) - (bottom, top) = self._range[ref] - return bottom, top - - def copy_range(self, src, dest): - self.assert_meaningful(src) - if src in self._range: - src_range = self._range[src] - else: - src_range = self.max_range(src) - self._range[dest] = src_range - - def invalidate_range(self, ref): - self.assert_meaningful(ref) - self._range[ref] = self.max_range(ref) - - def set_unmeaningful(self, *refs): - for ref in refs: - if ref in self._range: - del self._range[ref] - - def set_written(self, *refs): - """A "helper" method which does the following common sequence for - the given refs: asserts they're all writable, and sets them all - as touched and meaningful.""" - self.assert_writeable(*refs) - self.set_touched(*refs) - self.set_meaningful(*refs) - - def set_unwriteable(self, *refs): - """Intended to be used for implementing analyzing `for`.""" - for ref in refs: - self._writeable.remove(ref) - - def set_writeable(self, *refs): - """Intended to be used for implementing analyzing `for`, but also used in `save`.""" - for ref in refs: - self._writeable.add(ref) - - def encounter_gotos(self, gotos): - self._gotos_encountered |= gotos - - def encountered_gotos(self): - return self._gotos_encountered - - def set_terminated(self): - # Having a terminated context and having encountered gotos is not the same thing. - self._terminated = True - - def has_terminated(self): - return self._terminated - - def extract(self, location): - """Sets the given location as writeable in the context, and returns a 'baton' representing - the previous state of context for that location. This 'baton' can be used to later restore - this state of context.""" - # Used in `save`. - baton = ( - location, - location in self._touched, - self._range.get(location, None), - location in self._writeable, - ) - self.set_writeable(location) - return baton - - def re_introduce(self, baton): - """Given a 'baton' produced by `extract()`, restores the context for that saved location - to what it was before `extract()` was called.""" - # Used in `save`. - location, was_touched, was_range, was_writeable = baton - - if was_touched: - self._touched.add(location) - elif location in self._touched: - self._touched.remove(location) - - if was_range is not None: - self._range[location] = was_range - elif location in self._range: - del self._range[location] - - if was_writeable: - self._writeable.add(location) - elif location in self._writeable: - self._writeable.remove(location) - - def get_assoc(self, pointer): - return self._pointer_assoc.get(pointer) - - def set_assoc(self, pointer, table): - self._pointer_assoc[pointer] = table - - def is_constant(self, ref): - """read-only means that the program cannot change the value - of a location. constant means that the value of the location - will not change during the lifetime of the program.""" - if isinstance(ref, ConstantRef): - return True - if isinstance(ref, (IndirectRef, IndexedRef)): - return False - if isinstance(ref, LocationRef): - type_ = self.symtab.fetch_global_type(ref.name) - return isinstance(type_, RoutineType) - raise NotImplementedError - - def max_range(self, ref): - if isinstance(ref, ConstantRef): - return (ref.value, ref.value) - 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 - - class Analyzer(object): def __init__(self, symtab, debug=False): @@ -414,7 +100,7 @@ class Analyzer(object): if isinstance(ref, ConstantRef): return ref.type if not isinstance(ref, LocationRef): - raise NotImplementedError + raise NotImplementedError(str(ref)) return self.get_type_for_name(ref.name) def assert_type(self, type_, *locations): @@ -551,8 +237,10 @@ class Analyzer(object): self.analyze_save(instr, context) elif isinstance(instr, PointInto): self.analyze_point_into(instr, context) + elif isinstance(instr, Reset): + self.analyze_reset(instr, context) else: - raise NotImplementedError + raise NotImplementedError(str(instr)) def analyze_single_op(self, instr, context): @@ -772,11 +460,12 @@ class Analyzer(object): # 2. check that the context is meaningful if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): - context.assert_meaningful(src, REG_Y) + context.assert_meaningful(src, dest.ref, REG_Y) target = context.get_assoc(dest.ref) if not target: raise ForbiddenWriteError(instr, dest.ref) + context.assert_writeable(target) context.set_touched(target) context.set_written(target) @@ -788,10 +477,11 @@ class Analyzer(object): raise UnmeaningfulReadError(instr, src.ref) context.assert_meaningful(origin) + context.assert_writeable(dest) context.set_touched(dest) context.set_written(dest) elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef): - context.assert_meaningful(src.ref, REG_Y) + context.assert_meaningful(src.ref, dest.ref, REG_Y) origin = context.get_assoc(src.ref) if not origin: @@ -801,6 +491,7 @@ class Analyzer(object): target = context.get_assoc(dest.ref) if not target: raise ForbiddenWriteError(instr, dest.ref) + context.assert_writeable(target) context.set_touched(target) context.set_written(target) @@ -1020,11 +711,11 @@ class Analyzer(object): if context.get_assoc(instr.pointer): raise ForbiddenWriteError(instr, instr.pointer) - # associate pointer with table, mark it as meaningful. + # associate pointer with table + # (do not mark it as meaningful yet - that's reset's job.) context.set_assoc(instr.pointer, instr.table) - context.set_meaningful(instr.pointer) - context.set_touched(instr.pointer) + context.set_unmeaningful(instr.pointer) self.analyze_block(instr.block, context) if context.encountered_gotos(): @@ -1034,3 +725,21 @@ class Analyzer(object): context.set_assoc(instr.pointer, None) context.set_unmeaningful(instr.pointer) + + def analyze_reset(self, instr, context): + type = self.get_type(instr.pointer) + if not isinstance(type, (PointerType)): + raise TypeMismatchError(instr, instr.pointer.name) + + table = context.get_assoc(instr.pointer) + if not table: + raise ForbiddenWriteError(instr, '{} is not associated with any table'.format(instr.pointer.name)) + context.assert_meaningful(table) + low_limit, high_limit = context.get_range(table) + + assert isinstance(instr.offset, ConstantRef) + if instr.offset.value < low_limit or instr.offset.value > high_limit: + raise RangeExceededError(instr, instr.pointer.name) + + context.set_meaningful(instr.pointer) + context.set_touched(instr.pointer) diff --git a/src/sixtypical/ast.py b/src/sixtypical/ast.py index cbbf19c..bfaf621 100644 --- a/src/sixtypical/ast.py +++ b/src/sixtypical/ast.py @@ -75,6 +75,10 @@ class SingleOp(Instr): value_attrs = ('opcode', 'dest', 'src',) +class Reset(Instr): + value_attrs = ('pointer', 'offset',) + + class Call(Instr): value_attrs = ('location',) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 9e3f382..f9fac26 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -1,7 +1,7 @@ # encoding: UTF-8 from sixtypical.ast import ( - Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto + Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto ) from sixtypical.model import ( ConstantRef, LocationRef, IndexedRef, IndirectRef, @@ -37,6 +37,7 @@ class Compiler(object): self.routine_locals = {} # routine.name -> { local.name -> Label } self.labels = {} # global.name -> Label ("global" includes routines) self.trampolines = {} # Location -> Label + self.pointer_assoc = {} # pointer name -> table name (I'm not entirely happy about this) self.current_routine = None # - - - - helper methods - - - - @@ -191,6 +192,8 @@ class Compiler(object): return self.compile_save(instr) elif isinstance(instr, PointInto): return self.compile_point_into(instr) + elif isinstance(instr, Reset): + return self.compile_reset(instr) else: raise NotImplementedError @@ -741,12 +744,16 @@ class Compiler(object): self.emitter.emit(STA(Absolute(src_label))) def compile_point_into(self, instr): - src_label = self.get_label(instr.table.name) + self.pointer_assoc[instr.pointer.name] = instr.table.name + self.compile_block(instr.block) + del self.pointer_assoc[instr.pointer.name] + + def compile_reset(self, instr): + table_name = self.pointer_assoc[instr.pointer.name] + src_label = Offset(self.get_label(table_name), instr.offset.value) dest_label = self.get_label(instr.pointer.name) self.emitter.emit(LDA(Immediate(HighAddressByte(src_label)))) self.emitter.emit(STA(ZeroPage(dest_label))) self.emitter.emit(LDA(Immediate(LowAddressByte(src_label)))) self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1)))) - - self.compile_block(instr.block) diff --git a/src/sixtypical/context.py b/src/sixtypical/context.py new file mode 100644 index 0000000..a92c513 --- /dev/null +++ b/src/sixtypical/context.py @@ -0,0 +1,328 @@ +# encoding: UTF-8 + +from sixtypical.model import ( + RoutineType, ConstantRef, LocationRef, IndirectRef, IndexedRef, +) + + +class AnalysisContext(object): + """ + A location is touched if it was changed (or even potentially + changed) during this routine, or some routine called by this routine. + + A location is meaningful if it was an input to this routine, + or if it was set to a meaningful value by some operation in this + routine (or some routine called by this routine). + + If a location is meaningful, it has a range. This range represents + the lowest and highest values that it might possibly be (i.e. we know + it cannot possibly be below the lowest or above the highest.) In the + absence of any usage information, the range of a byte, is 0..255 and + the range of a word is 0..65535. + + A location is writeable if it was listed in the outputs and trashes + lists of this routine. A location can also be temporarily marked + unwriteable in certain contexts, such as `for` loops. + """ + def __init__(self, symtab, routine, inputs, outputs, trashes): + from sixtypical.analyzer import ConstantConstraintError, InconsistentConstraintsError + + self.symtab = symtab + self.routine = routine # Routine (AST node) + self._touched = set() # {LocationRef} + self._range = dict() # LocationRef -> (Int, Int) + self._writeable = set() # {LocationRef} + self._terminated = False + self._gotos_encountered = set() + self._pointer_assoc = dict() + + for ref in inputs: + if self.is_constant(ref): + raise ConstantConstraintError(self.routine, ref.name) + self._range[ref] = self.max_range(ref) + output_names = set() + for ref in outputs: + if self.is_constant(ref): + raise ConstantConstraintError(self.routine, ref.name) + output_names.add(ref.name) + self._writeable.add(ref) + for ref in trashes: + if self.is_constant(ref): + raise ConstantConstraintError(self.routine, ref.name) + if ref.name in output_names: + raise InconsistentConstraintsError(self.routine, ref.name) + self._writeable.add(ref) + + def __str__(self): + return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format( + self.__class__.__name__, + LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable) + ) + + def to_json_data(self): + type_ = self.symtab.fetch_global_type(self.routine.name) + return { + 'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)), + 'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)), + 'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)), + 'touched': ','.join(sorted(loc.name for loc in self._touched)), + 'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()), + 'writeable': ','.join(sorted(loc.name for loc in self._writeable)), + 'terminated': self._terminated, + 'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)), + } + + def clone(self): + c = AnalysisContext(self.symtab, self.routine, [], [], []) + c._touched = set(self._touched) + c._range = dict(self._range) + c._writeable = set(self._writeable) + c._pointer_assoc = dict(self._pointer_assoc) + c._gotos_encountered = set(self._gotos_encountered) + return c + + def update_from(self, other): + """Replaces the information in this context, with the information from the other context. + This is an overwriting action - it does not attempt to merge the contexts. + + We do not replace the gotos_encountered for technical reasons. (In `analyze_if`, + we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the + set of contexts we are updating from, and we want to retain our own.)""" + self.routine = other.routine + self._touched = set(other._touched) + self._range = dict(other._range) + self._writeable = set(other._writeable) + self._terminated = other._terminated + self._pointer_assoc = dict(other._pointer_assoc) + + def each_meaningful(self): + for ref in self._range.keys(): + yield ref + + def each_touched(self): + for ref in self._touched: + yield ref + + def each_writeable(self): + for ref in self._writeable: + yield ref + + def assert_meaningful(self, *refs, **kwargs): + from sixtypical.analyzer import UnmeaningfulReadError + + exception_class = kwargs.get('exception_class', UnmeaningfulReadError) + for ref in refs: + 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): + if ref not in self._range: + message = ref.name + if kwargs.get('message'): + message += ' (%s)' % kwargs['message'] + raise exception_class(self.routine, message) + elif isinstance(ref, IndexedRef): + self.assert_meaningful(ref.ref, **kwargs) + self.assert_meaningful(ref.index, **kwargs) + else: + raise NotImplementedError(ref) + + def assert_writeable(self, *refs, **kwargs): + from sixtypical.analyzer import ForbiddenWriteError + + exception_class = kwargs.get('exception_class', ForbiddenWriteError) + for ref in refs: + # locals are always writeable + if self.symtab.has_local(self.routine.name, ref.name): + continue + if ref not in self._writeable: + message = ref.name + if kwargs.get('message'): + message += ' (%s)' % kwargs['message'] + raise exception_class(self.routine, message) + + def assert_in_range(self, inside, outside, offset): + """Given two locations, assert that the first location, offset by the given offset, + is contained 'inside' the second location.""" + from sixtypical.analyzer import RangeExceededError + + assert isinstance(inside, LocationRef) + assert isinstance(outside, LocationRef) + + # inside should always be meaningful + inside_range = self._range[inside] + + # outside might not be meaningful, so default to max range if necessary + if outside in self._range: + outside_range = self._range[outside] + else: + outside_range = self.max_range(outside) + + if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]: + raise RangeExceededError(self.routine, + "Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format( + inside, inside_range, offset, outside, outside_range + ) + ) + + def set_touched(self, *refs): + for ref in refs: + self._touched.add(ref) + # TODO: it might be possible to invalidate the range here + + def set_meaningful(self, *refs): + for ref in refs: + if ref not in self._range: + self._range[ref] = self.max_range(ref) + + def set_top_of_range(self, ref, top): + self.assert_meaningful(ref) + (bottom, _) = self._range[ref] + self._range[ref] = (bottom, top) + + def set_bottom_of_range(self, ref, bottom): + self.assert_meaningful(ref) + (top, _) = self._range[ref] + self._range[ref] = (bottom, top) + + def set_range(self, ref, bottom, top): + self.assert_meaningful(ref) + self._range[ref] = (bottom, top) + + def get_top_of_range(self, ref): + if isinstance(ref, ConstantRef): + return ref.value + self.assert_meaningful(ref) + (_, top) = self._range[ref] + return top + + def get_bottom_of_range(self, ref): + if isinstance(ref, ConstantRef): + return ref.value + self.assert_meaningful(ref) + (bottom, _) = self._range[ref] + return bottom + + def get_range(self, ref): + if isinstance(ref, ConstantRef): + return (ref.value, ref.value) + self.assert_meaningful(ref) + (bottom, top) = self._range[ref] + return bottom, top + + def copy_range(self, src, dest): + self.assert_meaningful(src) + if src in self._range: + src_range = self._range[src] + else: + src_range = self.max_range(src) + self._range[dest] = src_range + + def invalidate_range(self, ref): + self.assert_meaningful(ref) + self._range[ref] = self.max_range(ref) + + def set_unmeaningful(self, *refs): + for ref in refs: + if ref in self._range: + del self._range[ref] + + def set_written(self, *refs): + """A "helper" method which does the following common sequence for + the given refs: asserts they're all writable, and sets them all + as touched and meaningful.""" + self.assert_writeable(*refs) + self.set_touched(*refs) + self.set_meaningful(*refs) + + def set_unwriteable(self, *refs): + """Intended to be used for implementing analyzing `for`.""" + for ref in refs: + self._writeable.remove(ref) + + def set_writeable(self, *refs): + """Intended to be used for implementing analyzing `for`, but also used in `save`.""" + for ref in refs: + self._writeable.add(ref) + + def encounter_gotos(self, gotos): + self._gotos_encountered |= gotos + + def encountered_gotos(self): + return self._gotos_encountered + + def set_terminated(self): + # Having a terminated context and having encountered gotos is not the same thing. + self._terminated = True + + def has_terminated(self): + return self._terminated + + def extract(self, location): + """Sets the given location as writeable in the context, and returns a 'baton' representing + the previous state of context for that location. This 'baton' can be used to later restore + this state of context.""" + # Used in `save`. + baton = ( + location, + location in self._touched, + self._range.get(location, None), + location in self._writeable, + ) + self.set_writeable(location) + return baton + + def re_introduce(self, baton): + """Given a 'baton' produced by `extract()`, restores the context for that saved location + to what it was before `extract()` was called.""" + # Used in `save`. + location, was_touched, was_range, was_writeable = baton + + if was_touched: + self._touched.add(location) + elif location in self._touched: + self._touched.remove(location) + + if was_range is not None: + self._range[location] = was_range + elif location in self._range: + del self._range[location] + + if was_writeable: + self._writeable.add(location) + elif location in self._writeable: + self._writeable.remove(location) + + def get_assoc(self, pointer): + return self._pointer_assoc.get(pointer) + + def set_assoc(self, pointer, table): + self._pointer_assoc[pointer] = table + + def is_constant(self, ref): + """read-only means that the program cannot change the value + of a location. constant means that the value of the location + will not change during the lifetime of the program.""" + if isinstance(ref, ConstantRef): + return True + if isinstance(ref, (IndirectRef, IndexedRef)): + return False + if isinstance(ref, LocationRef): + type_ = self.symtab.fetch_global_type(ref.name) + return isinstance(type_, RoutineType) + raise NotImplementedError + + def max_range(self, ref): + if isinstance(ref, ConstantRef): + return (ref.value, ref.value) + 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 diff --git a/src/sixtypical/emitter.py b/src/sixtypical/emitter.py index 5b4d346..e574640 100644 --- a/src/sixtypical/emitter.py +++ b/src/sixtypical/emitter.py @@ -128,7 +128,7 @@ class Offset(Emittable): class HighAddressByte(Emittable): def __init__(self, label): - assert isinstance(label, Label) + assert isinstance(label, (Label, Offset)) self.label = label def size(self): @@ -143,7 +143,7 @@ class HighAddressByte(Emittable): class LowAddressByte(Emittable): def __init__(self, label): - assert isinstance(label, Label) + assert isinstance(label, (Label, Offset)) self.label = label def size(self): diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 23ec757..0143f5f 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -1,7 +1,7 @@ # encoding: UTF-8 from sixtypical.ast import ( - Program, Defn, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto + Program, Defn, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto ) from sixtypical.model import ( TYPE_BIT, TYPE_BYTE, TYPE_WORD, @@ -432,6 +432,10 @@ class Parser(object): final = self.const() block = self.block() return For(self.scanner.line_number, dest=dest, direction=direction, final=final, block=block) + elif self.scanner.consume('reset'): + pointer = self.locexpr() + offset = self.const() + return Reset(self.scanner.line_number, pointer=pointer, offset=offset) elif self.scanner.token in ("ld",): # the same as add, sub, cmp etc below, except supports an indlocexpr for the src opcode = self.scanner.token diff --git a/test.sh b/test.sh index bed307e..c177d50 100755 --- a/test.sh +++ b/test.sh @@ -1,7 +1,9 @@ #!/bin/sh falderal --substring-error \ - tests/SixtyPical\ Syntax.md \ - tests/SixtyPical\ Analysis.md \ - tests/SixtyPical\ Fallthru.md \ - tests/SixtyPical\ Compilation.md + "tests/SixtyPical Syntax.md" \ + "tests/SixtyPical Analysis.md" \ + "tests/SixtyPical Storage.md" \ + "tests/SixtyPical Control Flow.md" \ + "tests/SixtyPical Fallthru.md" \ + "tests/SixtyPical Compilation.md" diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index b06a3a8..9af05e6 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -4,6 +4,10 @@ SixtyPical Analysis This is a test suite, written in [Falderal][] format, for the SixtyPical static analysis rules. +This file mostly contains tests for operations. +For rudiments and storage, see [SixtyPical Storage](SixtyPical%20Storage.md). +For control flow, see [SixtyPical Control Flow](SixtyPical%20Control%20Flow.md). + [Falderal]: http://catseye.tc/node/Falderal -> Functionality "Analyze SixtyPical program" is implemented by @@ -11,938 +15,6 @@ static analysis rules. -> Tests for functionality "Analyze SixtyPical program" -### Rudiments ### - -Routines must declare their inputs, outputs, and memory locations they trash. - - | define up routine - | inputs a - | outputs a - | trashes c, z, v, n - | { - | st off, c - | add a, 1 - | } - = ok - -Routines may not declare a memory location to be both an output and trashed. - - | define main routine - | outputs a - | trashes a - | { - | ld a, 0 - | } - ? InconsistentConstraintsError: a - -If a routine declares it outputs a location, that location should be initialized. - - | define main routine - | outputs a, x, z, n - | { - | ld x, 0 - | } - ? UnmeaningfulOutputError: a - - | define main routine - | inputs a - | outputs a - | { - | } - = ok - -If a routine declares it outputs a location, that location may or may not have -been initialized. Trashing is mainly a signal to the caller. - - | define main routine - | trashes x, z, n - | { - | ld x, 0 - | } - = ok - - | define main routine - | trashes x, z, n - | { - | } - = ok - -If a routine modifies a location, it needs to either output it or trash it. - - | define main routine - | { - | ld x, 0 - | } - ? ForbiddenWriteError: x - - | define main routine - | outputs x, z, n - | { - | ld x, 0 - | } - = ok - - | define main routine - | trashes x, z, n - | { - | ld x, 0 - | } - = ok - -This is true regardless of whether it's an input or not. - - | define main routine - | inputs x - | { - | ld x, 0 - | } - ? ForbiddenWriteError: x - - | define main routine - | inputs x - | outputs x, z, n - | { - | ld x, 0 - | } - = ok - - | define main routine - | inputs x - | trashes x, z, n - | { - | ld x, 0 - | } - = ok - -If a routine trashes a location, this must be declared. - - | define foo routine - | trashes x - | { - | trash x - | } - = ok - - | define foo routine - | { - | trash x - | } - ? ForbiddenWriteError: x - - | define foo routine - | outputs x - | { - | trash x - | } - ? UnmeaningfulOutputError: x - -If a routine causes a location to be trashed, this must be declared in the caller. - - | define trash_x routine - | trashes x, z, n - | { - | ld x, 0 - | } - | - | define foo routine - | trashes x, z, n - | { - | call trash_x - | } - = ok - - | define trash_x routine - | trashes x, z, n - | { - | ld x, 0 - | } - | - | define foo routine - | trashes z, n - | { - | call trash_x - | } - ? ForbiddenWriteError: x - - | define trash_x routine - | trashes x, z, n - | { - | ld x, 0 - | } - | - | define foo routine - | outputs x - | trashes z, n - | { - | call trash_x - | } - ? UnmeaningfulOutputError: x (in foo, line 12) - -If a routine reads or writes a user-define memory location, it needs to declare that too. - - | byte b1 @ 60000 - | byte b2 : 3 - | word w1 @ 60001 - | word w2 : 2000 - | - | define main routine - | inputs b1, w1 - | outputs b2, w2 - | trashes a, z, n - | { - | ld a, b1 - | st a, b2 - | copy w1, w2 - | } - = ok - -### call ### - -You can't call a non-routine. - - | byte up - | - | define main routine outputs x, y trashes z, n { - | ld x, 0 - | ld y, 1 - | call up - | } - ? TypeMismatchError: up - - | define main routine outputs x, y trashes z, n { - | ld x, 0 - | ld y, 1 - | call x - | } - ? TypeMismatchError: x - -Nor can you goto a non-routine. - - | byte foo - | - | define main routine { - | goto foo - | } - ? TypeMismatchError: foo - -### ld ### - -Can't `ld` from a memory location that isn't initialized. - - | define main routine - | inputs a, x - | trashes a, z, n - | { - | ld a, x - | } - = ok - - | define main routine - | inputs a - | trashes a - | { - | ld a, x - | } - ? UnmeaningfulReadError: x - -Can't `ld` to a memory location that doesn't appear in (outputs ∪ trashes). - - | define main routine - | trashes a, z, n - | { - | ld a, 0 - | } - = ok - - | define main routine - | outputs a - | trashes z, n - | { - | ld a, 0 - | } - = ok - - | define main routine - | outputs z, n - | trashes a - | { - | ld a, 0 - | } - = ok - - | define main routine - | trashes z, n - | { - | ld a, 0 - | } - ? ForbiddenWriteError: a - - | define main routine - | trashes a, n - | { - | ld a, 0 - | } - ? ForbiddenWriteError: z - -Can't `ld` a `word` type. - - | word foo - | - | define main routine - | inputs foo - | trashes a, n, z - | { - | ld a, foo - | } - ? TypeMismatchError: foo and a - -### st ### - -Can't `st` from a memory location that isn't initialized. - - | byte lives - | define main routine - | inputs x - | trashes lives - | { - | st x, lives - | } - = ok - - | byte lives - | define main routine - | trashes x, lives - | { - | st x, lives - | } - ? UnmeaningfulReadError: x - -Can't `st` to a memory location that doesn't appear in (outputs ∪ trashes). - - | byte lives - | define main routine - | trashes lives - | { - | st 0, lives - | } - = ok - - | byte lives - | define main routine - | outputs lives - | { - | st 0, lives - | } - = ok - - | byte lives - | define main routine - | inputs lives - | { - | st 0, lives - | } - ? ForbiddenWriteError: lives - -Can't `st` a `word` type. - - | word foo - | - | define main routine - | outputs foo - | trashes a, n, z - | { - | ld a, 0 - | st a, foo - | } - ? TypeMismatchError - -### tables ### - -Storing to a table, you must use an index. - - | byte one - | byte table[256] many - | - | define main routine - | outputs one - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, 0 - | st a, one - | } - = ok - - | byte one - | byte table[256] many - | - | define main routine - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, 0 - | st a, many - | } - ? TypeMismatchError - - | byte one - | byte table[256] many - | - | define main routine - | outputs one - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, 0 - | st a, one + x - | } - ? TypeMismatchError - - | byte one - | byte table[256] many - | - | define main routine - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, 0 - | st a, many + x - | } - = ok - -The index must be initialized. - - | byte one - | byte table[256] many - | - | define main routine - | outputs many - | trashes a, x, n, z - | { - | ld a, 0 - | st a, many + x - | } - ? UnmeaningfulReadError: x - -Reading from a table, you must use an index. - - | byte one - | - | define main routine - | outputs one - | trashes a, x, n, z - | { - | ld x, 0 - | st x, one - | ld a, one - | } - = ok - - | byte one - | - | define main routine - | outputs one - | trashes a, x, n, z - | { - | ld x, 0 - | st x, one - | ld a, one + x - | } - ? TypeMismatchError - - | byte table[256] many - | - | define main routine - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, 0 - | st a, many + x - | ld a, many - | } - ? TypeMismatchError - - | byte table[256] many - | - | define main routine - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, 0 - | st a, many + x - | ld a, many + x - | } - = ok - - | byte table[256] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, many + x - | } - = ok - -The index must be initialized. - - | byte table[256] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld a, many + x - | } - ? UnmeaningfulReadError: x - -Storing to a table, you may also include a constant offset. - - | byte one - | byte table[256] many - | - | define main routine - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, 0 - | st a, many + 100 + x - | } - = ok - -Reading from a table, you may also include a constant offset. - - | byte table[256] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, many + 100 + x - | } - = ok - -Using a constant offset, you can read and write entries in -the table beyond the 256th. - - | byte one - | byte table[1024] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | ld a, many + 999 + x - | st a, many + 1000 + x - | } - = ok - -There are other operations you can do on tables. (1/3) - - | byte table[256] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, c, n, z, v - | { - | ld x, 0 - | ld a, 0 - | st off, c - | add a, many + x - | sub a, many + x - | cmp a, many + x - | } - = ok - -There are other operations you can do on tables. (2/3) - - | byte table[256] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, c, n, z - | { - | ld x, 0 - | ld a, 0 - | and a, many + x - | or a, many + x - | xor a, many + x - | } - = ok - -There are other operations you can do on tables. (3/3) - - | byte table[256] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, c, n, z - | { - | ld x, 0 - | ld a, 0 - | st off, c - | shl many + x - | shr many + x - | inc many + x - | dec many + x - | } - = ok - -Copying to and from a word table. - - | word one - | word table[256] many - | - | define main routine - | inputs one, many - | outputs one, many - | trashes a, x, n, z - | { - | ld x, 0 - | copy one, many + x - | copy many + x, one - | } - = ok - - | word one - | word table[256] many - | - | define main routine - | inputs one, many - | outputs one, many - | trashes a, x, n, z - | { - | ld x, 0 - | copy one, many - | } - ? TypeMismatchError - - | word one - | word table[256] many - | - | define main routine - | inputs one, many - | outputs one, many - | trashes a, x, n, z - | { - | ld x, 0 - | copy one + x, many - | } - ? TypeMismatchError - -You can also copy a literal word to a word table. -(Even if the table has fewer than 256 entries.) - - | word table[32] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | copy 9999, many + x - | } - = ok - -Copying to and from a word table with a constant offset. - - | word one - | word table[256] many - | - | define main routine - | inputs one, many - | outputs one, many - | trashes a, x, n, z - | { - | ld x, 0 - | copy one, many + 100 + x - | copy many + 100 + x, one - | copy 9999, many + 1 + x - | } - = ok - -#### tables: range checking #### - -It is a static analysis error if it cannot be proven that a read or write -to a table falls within the defined size of that table. - -If a table has 256 entries, then there is never a problem (so long as -no constant offset is supplied), because a byte cannot index any entry -outside of 0..255. - -But if the table has fewer than 256 entries, or if a constant offset is -supplied, there is the possibility that the index will refer to an -entry in the table which does not exist. - -A SixtyPical implementation must be able to prove that the index is inside -the range of the table in various ways. The simplest is to show that a -constant value falls inside or outside the range of the table. - - | byte table[32] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 31 - | ld a, many + x - | st a, many + x - | } - = ok - - | byte table[32] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 32 - | ld a, many + x - | } - ? RangeExceededError - - | byte table[32] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 32 - | ld a, 0 - | st a, many + x - | } - ? RangeExceededError - -Any constant offset is taken into account in this check. - - | byte table[32] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 31 - | ld a, many + 1 + x - | } - ? RangeExceededError - - | byte table[32] many - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 31 - | ld a, 0 - | st a, many + 1 + x - | } - ? RangeExceededError - -This applies to `copy` as well. - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs many, one - | outputs many, one - | trashes a, x, n, z - | { - | ld x, 31 - | copy one, many + x - | copy many + x, one - | } - = ok - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs many, one - | outputs many, one - | trashes a, x, n, z - | { - | ld x, 32 - | copy many + x, one - | } - ? RangeExceededError - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs many, one - | outputs many, one - | trashes a, x, n, z - | { - | ld x, 32 - | copy one, many + x - | } - ? RangeExceededError - -Any constant offset is taken into account in this check. - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs many, one - | outputs many, one - | trashes a, x, n, z - | { - | ld x, 31 - | copy many + 1 + x, one - | } - ? RangeExceededError - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs many, one - | outputs many, one - | trashes a, x, n, z - | { - | ld x, 31 - | copy one, many + 1 + x - | } - ? RangeExceededError - -`AND`'ing a register with a value ensures the range of the -register will not exceed the range of the value. This can -be used to "clip" the range of an index so that it fits in -a table. - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs a, many, one - | outputs many, one - | trashes a, x, n, z - | { - | and a, 31 - | ld x, a - | copy one, many + x - | copy many + x, one - | } - = ok - -Tests for "clipping", but not enough. - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs a, many, one - | outputs many, one - | trashes a, x, n, z - | { - | and a, 63 - | ld x, a - | copy one, many + x - | copy many + x, one - | } - ? RangeExceededError - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs a, many, one - | outputs many, one - | trashes a, x, n, z - | { - | and a, 31 - | ld x, a - | copy one, many + 1 + x - | copy many + 1 + x, one - | } - ? RangeExceededError - -If you alter the value after "clipping" it, the range can -no longer be guaranteed. - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs a, many, one - | outputs many, one - | trashes a, x, n, z - | { - | and a, 31 - | ld x, a - | inc x - | copy one, many + x - | copy many + x, one - | } - ? RangeExceededError - -When the range of a location is known, incrementing or -decrementing that location's value will shift the known -range. It will not invalidate it unless the known range -is at the limits of the possible ranges for the type. - - | vector routine - | trashes a, z, n - | print - | - | vector (routine - | trashes a, z, n) - | table[32] vectors - | - | define main routine - | inputs vectors, print - | outputs vectors - | trashes print, a, x, z, n, c - | { - | ld x, 0 - | inc x - | copy print, vectors + x - | } - = ok - - | vector routine - | trashes a, z, n - | print - | - | vector (routine - | trashes a, z, n) - | table[32] vectors - | - | define main routine - | inputs vectors, print - | outputs vectors - | trashes print, a, x, z, n, c - | { - | ld x, 32 - | dec x - | copy print, vectors + x - | } - = ok - ### add ### Can't `add` from or to a memory location that isn't initialized. @@ -1624,1084 +696,6 @@ Some rudimentary tests for `nop`. | } = ok -### call ### - -When calling a routine, all of the locations it lists as inputs must be -initialized. - - | byte lives - | - | define foo routine - | inputs x - | trashes lives - | { - | st x, lives - | } - | - | define main routine - | { - | call foo - | } - ? UnmeaningfulReadError: x - -Note that if you call a routine that trashes a location, you also trash it. - - | byte lives - | - | define foo routine - | inputs x - | trashes lives - | { - | st x, lives - | } - | - | define main routine - | outputs x, z, n - | { - | ld x, 0 - | call foo - | } - ? ForbiddenWriteError: lives - - | byte lives - | - | define foo routine - | inputs x - | trashes lives - | { - | st x, lives - | } - | - | define main routine - | outputs x, z, n - | trashes lives - | { - | ld x, 0 - | call foo - | } - = ok - -You can't output a value that the thing you called trashed. - - | byte lives - | - | define foo routine - | inputs x - | trashes lives - | { - | st x, lives - | } - | - | define main routine - | outputs x, z, n, lives - | { - | ld x, 0 - | call foo - | } - ? UnmeaningfulOutputError: lives - -...unless you write to it yourself afterwards. - - | byte lives - | - | define foo routine - | inputs x - | trashes lives - | { - | st x, lives - | } - | - | define main routine - | outputs x, z, n, lives - | { - | ld x, 0 - | call foo - | st x, lives - | } - = ok - -If a routine declares outputs, they are initialized in the caller after -calling it. - - | define foo routine - | outputs x, z, n - | { - | ld x, 0 - | } - | - | define main routine - | outputs a - | trashes x, z, n - | { - | call foo - | ld a, x - | } - = ok - - | define foo routine - | { - | } - | - | define main routine - | outputs a - | trashes x - | { - | call foo - | ld a, x - | } - ? UnmeaningfulReadError: x - -If a routine trashes locations, they are uninitialized in the caller after -calling it. - - | define foo routine - | trashes x, z, n - | { - | ld x, 0 - | } - = ok - - | define foo routine - | trashes x, z, n - | { - | ld x, 0 - | } - | - | define main routine - | outputs a - | trashes x, z, n - | { - | call foo - | ld a, x - | } - ? UnmeaningfulReadError: x - -Calling an extern is just the same as calling a defined routine with the -same constraints. - - | define chrout routine - | inputs a - | trashes a - | @ 65490 - | - | define main routine - | trashes a, z, n - | { - | ld a, 65 - | call chrout - | } - = ok - - | define chrout routine - | inputs a - | trashes a - | @ 65490 - | - | define main routine - | trashes a, z, n - | { - | call chrout - | } - ? UnmeaningfulReadError: a - - | define chrout routine - | inputs a - | trashes a - | @ 65490 - | - | define main routine - | trashes a, x, z, n - | { - | ld a, 65 - | call chrout - | ld x, a - | } - ? UnmeaningfulReadError: a - -### trash ### - -Trash does nothing except indicate that we do not care about the value anymore. - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n - | { - | st a, x - | ld a, 0 - | trash a - | } - = ok - - | define foo routine - | inputs a - | outputs a, x - | trashes z, n - | { - | st a, x - | ld a, 0 - | trash a - | } - ? UnmeaningfulOutputError: a - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n - | { - | st a, x - | trash a - | st a, x - | } - ? UnmeaningfulReadError: a - -### if ### - -Both blocks of an `if` are analyzed. - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n, c - | { - | cmp a, 42 - | if z { - | ld x, 7 - | } else { - | ld x, 23 - | } - | } - = ok - -If a location is initialized in one block, it must be initialized in the other as well -in order to be considered to be initialized after the block. If it is not consistent, -it will be considered uninitialized. - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n, c - | { - | cmp a, 42 - | if z { - | ld x, 7 - | } else { - | ld a, 23 - | } - | } - ? UnmeaningfulOutputError: x - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n, c - | { - | cmp a, 42 - | if z { - | ld a, 6 - | } else { - | ld x, 7 - | } - | } - ? UnmeaningfulOutputError: x - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n, c - | { - | cmp a, 42 - | if not z { - | ld a, 6 - | } else { - | ld x, 7 - | } - | } - ? UnmeaningfulOutputError: x - - | define foo routine - | inputs a - | trashes a, x, z, n, c - | { - | cmp a, 42 - | if not z { - | ld a, 6 - | } else { - | ld x, 7 - | } - | ld a, x - | } - ? UnmeaningfulReadError: x - -If we don't care if it's uninitialized after the `if`, that's okay then. - - | define foo routine - | inputs a - | trashes a, x, z, n, c - | { - | cmp a, 42 - | if not z { - | ld a, 6 - | } else { - | ld x, 7 - | } - | } - = ok - -Or, if it does get initialized on both branches, that's okay then. - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n, c - | { - | cmp a, 42 - | if not z { - | ld x, 0 - | ld a, 6 - | } else { - | ld x, 7 - | } - | } - = ok - -However, this only pertains to initialization. If a value is already -initialized, either because it was set previous to the `if`, or is an -input to the routine, and it is initialized in one branch, it need not -be initialized in the other. - - | define foo routine - | outputs x - | trashes a, z, n, c - | { - | ld x, 0 - | ld a, 0 - | cmp a, 42 - | if z { - | ld x, 7 - | } else { - | ld a, 23 - | } - | } - = ok - - | define foo routine - | inputs x - | outputs x - | trashes a, z, n, c - | { - | ld a, 0 - | cmp a, 42 - | if z { - | ld x, 7 - | } else { - | ld a, 23 - | } - | } - = ok - -An `if` with a single block is analyzed as if it had an empty `else` block. - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n, c - | { - | cmp a, 42 - | if z { - | ld x, 7 - | } - | } - ? UnmeaningfulOutputError: x - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n, c - | { - | ld x, 0 - | cmp a, 42 - | if z { - | ld x, 7 - | } - | } - = ok - - | define foo routine - | inputs a - | outputs x - | trashes a, z, n, c - | { - | ld x, 0 - | cmp a, 42 - | if not z { - | ld x, 7 - | } - | } - = ok - -The cardinal rule for trashes in an `if` is the "union rule": if one branch -trashes {`a`} and the other branch trashes {`b`} then the whole `if` statement -trashes {`a`, `b`}. - - | define foo routine - | inputs a, x, z - | trashes a, x - | { - | if z { - | trash a - | } else { - | trash x - | } - | } - = ok - - | define foo routine - | inputs a, x, z - | trashes a - | { - | if z { - | trash a - | } else { - | trash x - | } - | } - ? ForbiddenWriteError: x (in foo, line 10) - - | define foo routine - | inputs a, x, z - | trashes x - | { - | if z { - | trash a - | } else { - | trash x - | } - | } - ? ForbiddenWriteError: a (in foo, line 10) - -### repeat ### - -Repeat loop. - - | define main routine - | outputs x, y, n, z, c - | { - | ld x, 0 - | ld y, 15 - | repeat { - | inc x - | inc y - | cmp x, 10 - | } until z - | } - = ok - -You can initialize something inside the loop that was uninitialized outside. - - | define main routine - | outputs x, y, n, z, c - | { - | ld x, 0 - | repeat { - | ld y, 15 - | inc x - | cmp x, 10 - | } until z - | } - = ok - -But you can't UNinitialize something at the end of the loop that you need -initialized at the start. - - | define foo routine - | trashes y - | { - | } - | - | define main routine - | outputs x, y, n, z, c - | { - | ld x, 0 - | ld y, 15 - | repeat { - | inc x - | inc y - | call foo - | cmp x, 10 - | } until z - | } - ? UnmeaningfulReadError: y - -And if you trash the test expression (i.e. `z` in the below) inside the loop, -this is an error too. - - | word one : 0 - | word two : 0 - | - | define main routine - | inputs one, two - | outputs two - | trashes a, z, n - | { - | repeat { - | copy one, two - | } until z - | } - ? UnmeaningfulReadError: z - -The body of `repeat forever` can be empty. - - | define main routine - | { - | repeat { - | } forever - | } - = ok - -While `repeat` is most often used with `z`, it can also be used with `n`. - - | define main routine - | outputs y, n, z - | { - | ld y, 15 - | repeat { - | dec y - | } until n - | } - = ok - -### for ### - -Basic "open-faced for" loop. We'll start with the "upto" variant. - -#### upward-counting variant - -Even though we do not give the starting value in the "for" construct, -we know the exact range the loop variable takes on. - - | byte table[16] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | ld x, 0 - | for x up to 15 { - | ld a, tab + x - | } - | } - = ok - - | byte table[15] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | ld x, 0 - | for x up to 15 { - | ld a, tab + x - | } - | } - ? RangeExceededError - -You need to initialize the loop variable before the loop. - - | byte table[16] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | for x up to 15 { - | ld a, 0 - | } - | } - ? UnmeaningfulReadError - -Because routines currently do not include range constraints, -the loop variable may not be useful as an input (the location -is assumed to have the maximum range.) - - | byte table[16] tab - | - | define foo routine - | inputs tab, x - | trashes a, x, c, z, v, n { - | for x up to 15 { - | ld a, 0 - | } - | } - ? RangeExceededError - -You cannot modify the loop variable in a "for" loop. - - | byte table[16] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | ld x, 0 - | for x up to 15 { - | ld x, 0 - | } - | } - ? ForbiddenWriteError - -This includes nesting a loop on the same variable. - - | byte table[16] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | ld x, 0 - | for x up to 8 { - | for x up to 15 { - | ld a, 0 - | } - | } - | } - ? ForbiddenWriteError - -But nesting with two different variables is okay. - - | byte table[16] tab - | - | define foo routine inputs tab trashes a, x, y, c, z, v, n { - | ld x, 0 - | for x up to 8 { - | ld a, x - | ld y, a - | for y up to 15 { - | ld a, 0 - | } - | } - | } - = ok - -Inside the inner loop, the outer variable is still not writeable. - - | byte table[16] tab - | - | define foo routine inputs tab trashes a, x, y, c, z, v, n { - | ld x, 0 - | for x up to 8 { - | ld a, x - | ld y, a - | for y up to 15 { - | ld x, 0 - | } - | } - | } - ? ForbiddenWriteError - -If the range isn't known to be smaller than the final value, you can't go up to it. - - | byte table[32] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | ld x, 16 - | for x up to 15 { - | ld a, tab + x - | } - | } - ? RangeExceededError - -You can initialize something inside the loop that was uninitialized outside. - - | define main routine - | outputs x, y, n, z - | trashes c - | { - | ld x, 0 - | for x up to 15 { - | ld y, 15 - | } - | } - = ok - -But you can't UNinitialize something at the end of the loop that you need -initialized at the start of that loop. - - | define foo routine - | trashes y - | { - | } - | - | define main routine - | outputs x, y, n, z - | trashes c - | { - | ld x, 0 - | ld y, 15 - | for x up to 15 { - | inc y - | call foo - | } - | } - ? UnmeaningfulReadError: y - -The "for" loop does not preserve the `z` or `n` registers. - - | define foo routine trashes x { - | ld x, 0 - | for x up to 15 { - | } - | } - ? ForbiddenWriteError - -But it does preserve the other registers, such as `c`. - - | define foo routine trashes x, z, n { - | ld x, 0 - | for x up to 15 { - | } - | } - = ok - -In fact it does not strictly trash `z` and `n`, as they are -always set to known values after the loop. TODO: document -what these known values are! - - | define foo routine outputs z, n trashes x { - | ld x, 0 - | for x up to 15 { - | } - | } - = ok - -#### downward-counting variant - -In a "for" loop (downward-counting variant), we know the exact range the loop variable takes on. - - | byte table[16] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | ld x, 15 - | for x down to 0 { - | ld a, tab + x - | } - | } - = ok - - | byte table[15] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | ld x, 15 - | for x down to 0 { - | ld a, tab + x - | } - | } - ? RangeExceededError - -You need to initialize the loop variable before a "for" loop (downward variant). - - | byte table[16] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | for x down to 15 { - | ld a, 0 - | } - | } - ? UnmeaningfulReadError - -You cannot modify the loop variable in a "for" loop (downward variant). - - | byte table[16] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | ld x, 15 - | for x down to 0 { - | ld x, 0 - | } - | } - ? ForbiddenWriteError - -If the range isn't known to be larger than the final value, you can't go down to it. - - | byte table[32] tab - | - | define foo routine inputs tab trashes a, x, c, z, v, n { - | ld x, 0 - | for x down to 0 { - | ld a, tab + x - | } - | } - ? RangeExceededError - -The "for" loop does not preserve the `z` or `n` registers. - - | define foo routine trashes x { - | ld x, 15 - | for x down to 0 { - | } - | } - ? ForbiddenWriteError - -But it does preserve the other registers, such as `c`. - - | define foo routine trashes x, z, n { - | ld x, 15 - | for x down to 0 { - | } - | } - = ok - -In fact it does not strictly trash `z` and `n`, as they are -always set to known values after the loop. TODO: document -what these known values are! - - | define foo routine outputs z, n trashes x { - | ld x, 15 - | for x down to 0 { - | } - | } - = ok - -### save ### - -Basic neutral test, where the `save` makes no difference. - - | define main routine - | inputs a, x - | outputs a, x - | trashes z, n - | { - | ld a, 1 - | save x { - | ld a, 2 - | } - | ld a, 3 - | } - = ok - -Saving any location (other than `a`) will trash `a`. - - | define main routine - | inputs a, x - | outputs a, x - | trashes z, n - | { - | ld a, 1 - | save x { - | ld a, 2 - | } - | } - ? UnmeaningfulOutputError - -Saving `a` does not trash anything. - - | define main routine - | inputs a, x - | outputs a, x - | trashes z, n - | { - | ld x, 1 - | save a { - | ld x, 2 - | } - | ld x, 3 - | } - = ok - -A defined value that has been saved can be trashed inside the block. -It will continue to be defined outside the block. - - | define main routine - | outputs x, y - | trashes a, z, n - | { - | ld x, 0 - | save x { - | ld y, 0 - | trash x - | } - | } - = ok - -A trashed value that has been saved can be used inside the block. -It will continue to be trashed outside the block. - -(Note, both x and a are unmeaningful in this test.) - - | define main routine - | inputs a - | outputs a, x - | trashes z, n - | { - | ld x, 0 - | trash x - | save x { - | ld a, 0 - | ld x, 1 - | } - | } - ? UnmeaningfulOutputError - -The known range of a value will be preserved outside the block as well. - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs a, many, one - | outputs many, one - | trashes a, x, n, z - | { - | and a, 31 - | ld x, a - | save x { - | ld x, 255 - | } - | copy one, many + x - | copy many + x, one - | } - = ok - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs a, many, one - | outputs many, one - | trashes a, x, n, z - | { - | and a, 63 - | ld x, a - | save x { - | ld x, 1 - | } - | copy one, many + x - | copy many + x, one - | } - ? RangeExceededError - -The known properties of a value are preserved inside the block, too. - - | word one: 77 - | word table[32] many - | - | define main routine - | inputs a, many, one - | outputs many, one - | trashes a, x, n, z - | { - | and a, 31 - | ld x, a - | save x { - | copy one, many + x - | copy many + x, one - | } - | copy one, many + x - | copy many + x, one - | } - = ok - -A value which is not output from the routine, is preserved by the -routine; and can appear in a `save` exactly because a `save` preserves it. - - | define main routine - | outputs y - | trashes a, z, n - | { - | save x { - | ld y, 0 - | ld x, 1 - | } - | } - = ok - -Because saving anything except `a` trashes `a`, a common idiom is to save `a` -first in a nested series of `save`s. - - | define main routine - | inputs a - | outputs a - | trashes z, n - | { - | save a { - | save x { - | ld a, 0 - | ld x, 1 - | } - | } - | } - = ok - -There is a shortcut syntax for a nested series of `save`s. - - | define main routine - | inputs a - | outputs a - | trashes z, n - | { - | save a, x { - | ld a, 0 - | ld x, 1 - | } - | } - = ok - -`a` is only preserved if it is the outermost thing `save`d. - - | define main routine - | inputs a - | outputs a - | trashes z, n - | { - | save x, a { - | ld a, 0 - | ld x, 1 - | } - | } - ? UnmeaningfulOutputError: a - -Not just registers, but also user-defined locations can be saved. - - | byte foo - | - | define main routine - | trashes a, z, n - | { - | save foo { - | st 5, foo - | } - | } - = ok - -But only if they are bytes. - - | word foo - | - | define main routine - | trashes a, z, n - | { - | save foo { - | copy 555, foo - | } - | } - ? TypeMismatchError - - | byte table[16] tab - | - | define main routine - | trashes a, y, z, n - | { - | save tab { - | ld y, 0 - | st 5, tab + y - | } - | } - ? TypeMismatchError - -A `goto` cannot appear within a `save` block. - - | define other routine - | trashes a, z, n - | { - | ld a, 0 - | } - | - | define main routine - | trashes a, z, n - | { - | ld a, 1 - | save x { - | ld x, 2 - | goto other - | } - | } - ? IllegalJumpError - ### with interrupts ### | vector routine @@ -2760,1445 +754,6 @@ A `goto` cannot appear within a `with interrupts` block. | } ? IllegalJumpError -### copy ### - -Can't `copy` from a memory location that isn't initialized. - - | byte lives - | define main routine - | inputs x - | outputs lives - | trashes a, z, n - | { - | copy x, lives - | } - = ok - - | byte lives - | define main routine - | outputs lives - | trashes x, a, z, n - | { - | copy x, lives - | } - ? UnmeaningfulReadError: x - -Can't `copy` to a memory location that doesn't appear in (outputs ∪ trashes). - - | byte lives - | define main routine - | trashes lives, a, z, n - | { - | copy 0, lives - | } - = ok - - | byte lives - | define main routine - | outputs lives - | trashes a, z, n - | { - | copy 0, lives - | } - = ok - - | byte lives - | define main routine - | inputs lives - | trashes a, z, n - | { - | copy 0, lives - | } - ? ForbiddenWriteError: lives - -a, z, and n are trashed, and must be declared as such. - -(Note, both n and z are forbidden writes in this test.) - - | byte lives - | define main routine - | outputs lives - | { - | copy 0, lives - | } - ? ForbiddenWriteError - -a, z, and n are trashed, and must not be declared as outputs. - -(Note, both n and a are unmeaningful outputs in this test.) - - | byte lives - | define main routine - | outputs lives, a, z, n - | { - | copy 0, lives - | } - ? UnmeaningfulOutputError - -Unless of course you subsequently initialize them. - - | byte lives - | define main routine - | outputs lives, a, z, n - | { - | copy 0, lives - | ld a, 0 - | } - = ok - -Can `copy` from a `byte` to a `byte`. - - | byte source : 0 - | byte dest - | - | define main routine - | inputs source - | outputs dest - | trashes a, z, n - | { - | copy source, dest - | } - = ok - -The understanding is that, because `copy` trashes `a`, `a` cannot be used -as the destination of a `copy`. - - | byte source : 0 - | byte dest - | - | define main routine - | inputs source - | outputs dest - | trashes a, z, n - | { - | copy source, a - | } - ? ForbiddenWriteError - -Can `copy` from a `word` to a `word`. - - | word source : 0 - | word dest - | - | define main routine - | inputs source - | outputs dest - | trashes a, z, n - | { - | copy source, dest - | } - = ok - -Can't `copy` from a `byte` to a `word`. - - | byte source : 0 - | word dest - | - | define main routine - | inputs source - | outputs dest - | trashes a, z, n - | { - | copy source, dest - | } - ? TypeMismatchError - -Can't `copy` from a `word` to a `byte`. - - | word source : 0 - | byte dest - | - | define main routine - | inputs source - | outputs dest - | trashes a, z, n - | { - | copy source, dest - | } - ? TypeMismatchError - -### point ... into blocks ### - -Pointer must be a pointer type. - - | byte table[256] tab - | word ptr - | - | define main routine - | inputs tab - | outputs y, tab - | trashes a, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | copy 123, [ptr] + y - | } - | } - ? TypeMismatchError - -Cannot write through pointer outside a `point ... into` block. - - | byte table[256] tab - | pointer ptr - | - | define main routine - | inputs tab, ptr - | outputs y, tab - | trashes a, z, n, ptr - | { - | ld y, 0 - | copy 123, [ptr] + y - | } - ? ForbiddenWriteError - - | byte table[256] tab - | pointer ptr - | - | define main routine - | inputs tab - | outputs y, tab - | trashes a, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | copy 123, [ptr] + y - | } - | copy 123, [ptr] + y - | } - ? ForbiddenWriteError - -Write literal through a pointer into a table. - - | byte table[256] tab - | pointer ptr - | - | define main routine - | inputs tab - | outputs y, tab - | trashes a, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | copy 123, [ptr] + y - | } - | } - = ok - -Writing into a table via a pointer does use `y`. - - | byte table[256] tab - | pointer ptr - | - | define main routine - | inputs tab - | outputs tab - | trashes a, z, n, ptr - | { - | point ptr into tab { - | copy 123, [ptr] + y - | } - | } - ? UnmeaningfulReadError - -Write stored value through a pointer into a table. - - | byte table[256] tab - | pointer ptr - | byte foo - | - | define main routine - | inputs foo, tab - | outputs y, tab - | trashes a, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | copy foo, [ptr] + y - | } - | } - = ok - -Read a table entry via a pointer. - - | byte table[256] tab - | pointer ptr - | byte foo - | - | define main routine - | inputs tab - | outputs foo - | trashes a, y, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | copy [ptr] + y, foo - | } - | } - = ok - -Read and write through two pointers into a table. - - | byte table[256] tab - | pointer ptra - | pointer ptrb - | - | define main routine - | inputs tab - | outputs tab - | trashes a, y, z, n, ptra, ptrb - | { - | ld y, 0 - | point ptra into tab { - | point ptrb into tab { - | copy [ptra] + y, [ptrb] + y - | } - | } - | } - = ok - -Read through a pointer into a table, to the `a` register. Note that this is done with `ld`, -not `copy`. - - | byte table[256] tab - | pointer ptr - | byte foo - | - | define main routine - | inputs tab - | outputs a - | trashes y, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | ld a, [ptr] + y - | } - | } - = ok - -Write the `a` register through a pointer into a table. Note that this is done with `st`, -not `copy`. - - | byte table[256] tab - | pointer ptr - | byte foo - | - | define main routine - | inputs tab - | outputs tab - | trashes a, y, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | ld a, 255 - | st a, [ptr] + y - | } - | } - = ok - -Cannot get a pointer into a non-byte (for instance, word) table. - - | word table[256] tab - | pointer ptr - | byte foo - | - | define main routine - | inputs tab - | outputs foo - | trashes a, y, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | copy [ptr] + y, foo - | } - | } - ? TypeMismatchError - -Cannot get a pointer into a non-byte (for instance, vector) table. - - | vector (routine trashes a, z, n) table[256] tab - | pointer ptr - | vector (routine trashes a, z, n) foo - | - | define main routine - | inputs tab - | outputs foo - | trashes a, y, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | copy [ptr] + y, foo - | } - | } - ? TypeMismatchError - -`point into` by itself only requires `ptr` to be writeable. By itself, -it does not require `tab` to be readable or writeable. - - | byte table[256] tab - | pointer ptr - | - | define main routine - | trashes a, z, n, ptr - | { - | point ptr into tab { - | ld a, 0 - | } - | } - = ok - - | byte table[256] tab - | pointer ptr - | - | define main routine - | trashes a, z, n - | { - | point ptr into tab { - | ld a, 0 - | } - | } - ? ForbiddenWriteError - -After a `point into` block, the pointer is no longer meaningful and cannot -be considered an output of the routine. - - | byte table[256] tab - | pointer ptr - | - | define main routine - | inputs tab - | outputs y, tab, ptr - | trashes a, z, n - | { - | ld y, 0 - | point ptr into tab { - | copy 123, [ptr] + y - | } - | } - ? UnmeaningfulOutputError - -If code in a routine reads from a table through a pointer, the table must be in -the `inputs` of that routine. - - | byte table[256] tab - | pointer ptr - | byte foo - | - | define main routine - | outputs foo - | trashes a, y, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | copy [ptr] + y, foo - | } - | } - ? UnmeaningfulReadError - -Likewise, if code in a routine writes into a table via a pointer, the table must -be in the `outputs` of that routine. - - | byte table[256] tab - | pointer ptr - | - | define main routine - | inputs tab - | trashes a, y, z, n, ptr - | { - | ld y, 0 - | point ptr into tab { - | copy 123, [ptr] + y - | } - | } - ? ForbiddenWriteError - -If code in a routine reads from a table through a pointer, the pointer *should* -remain inside the range of the table. This is currently not checked. - - | byte table[32] tab - | pointer ptr - | byte foo - | - | define main routine - | inputs tab - | outputs foo - | trashes a, y, c, z, n, v, ptr - | { - | ld y, 0 - | point ptr into tab { - | st off, c - | add ptr, word 100 - | copy [ptr] + y, foo - | } - | } - = ok - -Likewise, if code in a routine writes into a table through a pointer, the pointer -*should* remain inside the range of the table. This is currently not checked. - - | byte table[32] tab - | pointer ptr - | - | define main routine - | inputs tab - | outputs tab - | trashes a, y, c, z, n, v, ptr - | { - | ld y, 0 - | point ptr into tab { - | st off, c - | add ptr, word 100 - | copy 123, [ptr] + y - | } - | } - = ok - -### routines ### - -Routines are constants. You need not, and in fact cannot, specify a constant -as an input to, an output of, or as a trashed value of a routine. - - | vector routine - | inputs x - | outputs x - | trashes z, n - | vec - | - | define foo routine - | inputs x - | outputs x - | trashes z, n - | { - | inc x - | } - | - | define main routine - | inputs foo - | outputs vec - | trashes a, z, n - | { - | copy foo, vec - | } - ? ConstantConstraintError: foo - - | vector routine - | inputs x - | outputs x - | trashes z, n - | vec - | - | define foo routine - | inputs x - | outputs x - | trashes z, n - | { - | inc x - | } - | - | define main routine - | outputs vec, foo - | trashes a, z, n - | { - | copy foo, vec - | } - ? ConstantConstraintError: foo - - | vector routine - | inputs x - | outputs x - | trashes z, n - | vec - | - | define foo routine - | inputs x - | outputs x - | trashes z, n - | { - | inc x - | } - | - | define main routine - | outputs vec - | trashes a, z, n, foo - | { - | copy foo, vec - | } - ? ConstantConstraintError: foo - -#### routine-vector type compatibility - -You can copy the address of a routine into a vector, if that vector type -is at least as "wide" as the type of the routine. More specifically, - -- the vector must take _at least_ the inputs that the routine takes -- the vector must produce _at least_ the outputs that the routine produces -- the vector must trash _at least_ what the routine trashes - -If the vector and the routine have the very same signature, that's not an error. - - | vector routine - | inputs x, y - | outputs x, y - | trashes z, n - | vec - | - | define foo routine - | inputs x, y - | outputs x, y - | trashes z, n - | { - | inc x - | inc y - | } - | - | define main routine - | outputs vec - | trashes a, z, n - | { - | copy foo, vec - | } - = ok - -If the vector takes an input that the routine doesn't take, that's not an error. -(The interface requires that a parameter be specified before calling, but the -implementation doesn't actually read it.) - - | vector routine - | inputs x, y, a - | outputs x, y - | trashes z, n - | vec - | - | define foo routine - | inputs x, y - | outputs x, y - | trashes z, n - | { - | inc x - | inc y - | } - | - | define main routine - | outputs vec - | trashes a, z, n - | { - | copy foo, vec - | } - = ok - -If the vector fails to take an input that the routine takes, that's an error. - - | vector routine - | inputs x - | outputs x, y - | trashes z, n - | vec - | - | define foo routine - | inputs x, y - | outputs x, y - | trashes z, n - | { - | inc x - | inc y - | } - | - | define main routine - | outputs vec - | trashes a, z, n - | { - | copy foo, vec - | } - ? IncompatibleConstraintsError - -If the vector produces an output that the routine doesn't produce, that's not an error. -(The interface claims the result of calling the routine is defined, but the implementation -actually preserves it instead of changing it; the caller can still treat it as a defined -output.) - - | vector routine - | inputs x, y - | outputs x, y, a - | trashes z, n - | vec - | - | define foo routine - | inputs x, y - | outputs x, y - | trashes z, n - | { - | inc x - | inc y - | } - | - | define main routine - | outputs vec - | trashes a, z, n - | { - | copy foo, vec - | } - = ok - -If the vector fails to produce an output that the routine produces, that's an error. - - | vector routine - | inputs x, y - | outputs x - | trashes z, n - | vec - | - | define foo routine - | inputs x, y - | outputs x, y - | trashes z, n - | { - | inc x - | inc y - | } - | - | define main routine - | outputs vec - | trashes a, z, n - | { - | copy foo, vec - | } - ? IncompatibleConstraintsError - -If the vector fails to trash something the routine trashes, that's an error. - - | vector routine - | inputs x, y - | outputs x, y - | trashes z - | vec - | - | define foo routine - | inputs x, y - | outputs x, y - | trashes z, n - | { - | inc x - | inc y - | } - | - | define main routine - | outputs vec - | trashes a, z, n - | { - | copy foo, vec - | } - ? IncompatibleConstraintsError - -If the vector trashes something the routine doesn't trash, that's not an error. -(The implementation preserves something the interface doesn't guarantee is -preserved. The caller gets no guarantee that it's preserved. It actually is, -but it doesn't know that.) - - | vector routine - | inputs x, y - | outputs x, y - | trashes a, z, n - | vec - | - | define foo routine - | inputs x, y - | outputs x, y - | trashes z, n - | { - | inc x - | inc y - | } - | - | define main routine - | outputs vec - | trashes a, z, n - | { - | copy foo, vec - | } - = ok - -#### other properties of routines - -Routines are read-only. - - | vector routine - | inputs x - | outputs x - | trashes z, n - | vec - | - | define foo routine - | inputs x - | outputs x - | trashes z, n - | { - | inc x - | } - | - | define main routine - | outputs vec - | trashes a, z, n - | { - | copy vec, foo - | } - ? TypeMismatchError - -Indirect call. - - | vector routine - | outputs x trashes z, n - | foo - | - | define bar routine outputs x trashes z, n { - | ld x, 200 - | } - | - | define main routine outputs x, foo trashes a, z, n { - | copy bar, foo - | call foo - | } - = ok - -Calling the vector does indeed trash the things the vector says it does. - - | vector routine trashes x, z, n foo - | - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine outputs x, foo trashes z, n { - | ld x, 0 - | copy bar, foo - | call foo - | } - ? UnmeaningfulOutputError: x - -For now at least, you cannot have a `goto` inside a `repeat` loop. - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | repeat { - | inc x - | goto bar - | } until z - | } - ? IllegalJumpError - -`goto`, as a matter of syntax, can only appear at the end -of a block; but it need not be the final instruction in a -routine. - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | goto bar - | } - = ok - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } - | } - = ok - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } - | goto bar - | } - = ok - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } - | ld x, 0 - | } - = ok - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } else { - | ld x, 0 - | goto bar - | } - | } - = ok - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } else { - | ld x, 0 - | } - | } - = ok - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } else { - | ld x, 0 - | } - | ld x, 0 - | } - = ok - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } else { - | ld x, 0 - | } - | goto bar - | } - = ok - -Even though `goto` can only appear at the end of a block, -you can still wind up with dead code; the analysis detects -this. - - | define bar routine trashes x, z, n { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } else { - | ld x, 0 - | goto bar - | } - | ld x, 100 - | } - ? TerminatedContextError - -It is important that the type context at every -`goto` is compatible with the type context at the end of -the routine. - - | define bar routine - | inputs x - | trashes x, z, n - | { - | ld x, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } else { - | ld x, 0 - | } - | ld x, 1 - | } - = ok - -Here, we try to trash `x` before `goto`ing a routine that inputs `x`. - - | define bar routine - | inputs x - | trashes x, z, n - | { - | ld x, 200 - | } - | - | define main routine - | outputs a - | trashes x, z, n - | { - | ld x, 0 - | if z { - | trash x - | goto bar - | } else { - | trash x - | } - | ld a, 1 - | } - ? UnmeaningfulReadError: x - -Here, we declare that main outputs `a`, but we `goto` a routine that does not output `a`. - - | define bar routine - | inputs x - | trashes x, z, n - | { - | ld x, 200 - | } - | - | define main routine - | outputs a - | trashes x, z, n - | { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } else { - | ld x, 2 - | } - | ld a, 1 - | } - ? UnmeaningfulOutputError: a - -Here, we declare that main outputs a, and we goto a routine that outputs a so that's OK. - - | define bar routine - | inputs x - | outputs a - | trashes x, z, n - | { - | ld x, 200 - | ld a, 1 - | } - | - | define main routine - | outputs a - | trashes x, z, n - | { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar - | } else { - | ld x, 2 - | } - | ld a, 1 - | } - = ok - -Here, we declare that main outputs `a`, and we `goto` two routines, and they both output `a`. - - | define bar0 routine - | inputs x - | outputs a - | trashes x, z, n - | { - | ld a, x - | } - | - | define bar1 routine - | inputs x - | outputs a - | trashes x, z, n - | { - | ld a, 200 - | } - | - | define main routine - | outputs a - | trashes x, z, n - | { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar0 - | } else { - | ld x, 2 - | goto bar1 - | } - | } - = ok - -Here is like just above, but one routine doesn't output `a`. - - | define bar0 routine - | inputs x - | outputs a - | trashes x, z, n - | { - | ld a, x - | } - | - | define bar1 routine - | inputs x - | trashes x, z, n - | { - | ld x, 200 - | } - | - | define main routine - | outputs a - | trashes x, z, n - | { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar0 - | } else { - | ld x, 2 - | goto bar1 - | } - | } - ? InconsistentExitError - -Here is like the above, but the two routines have different inputs, and that's OK. - - | define bar0 routine - | inputs x - | outputs a - | trashes x, z, n - | { - | ld a, x - | } - | - | define bar1 routine - | outputs a - | trashes x, z, n - | { - | ld a, 200 - | } - | - | define main routine - | outputs a - | trashes x, z, n - | { - | ld x, 0 - | if z { - | ld x, 1 - | goto bar0 - | } else { - | ld x, 2 - | goto bar1 - | } - | } - = ok - -Another inconsistent exit test, this one based on "real" code -(the `ribos2` demo). - - | typedef routine - | inputs border_color, vic_intr - | outputs border_color, vic_intr - | trashes a, z, n, c - | irq_handler - | - | vector irq_handler cinv @ $314 - | vector irq_handler saved_irq_vec - | byte vic_intr @ $d019 - | byte border_color @ $d020 - | - | define pla_tay_pla_tax_pla_rti routine - | inputs a - | trashes a - | @ $EA81 - | - | define our_service_routine irq_handler - | { - | ld a, vic_intr - | st a, vic_intr - | and a, 1 - | cmp a, 1 - | if not z { - | goto saved_irq_vec - | } else { - | ld a, border_color - | xor a, $ff - | st a, border_color - | goto pla_tay_pla_tax_pla_rti - | } - | } - | - | define main routine - | { - | } - ? InconsistentExitError - - | typedef routine - | inputs border_color, vic_intr - | outputs border_color, vic_intr - | trashes a, z, n, c - | irq_handler - | - | vector irq_handler cinv @ $314 - | vector irq_handler saved_irq_vec - | byte vic_intr @ $d019 - | byte border_color @ $d020 - | - | define pla_tay_pla_tax_pla_rti routine - | inputs border_color, vic_intr - | outputs border_color, vic_intr - | trashes a, z, n, c - | @ $EA81 - | - | define our_service_routine irq_handler - | { - | ld a, vic_intr - | st a, vic_intr - | and a, 1 - | cmp a, 1 - | if not z { - | goto saved_irq_vec - | } else { - | ld a, border_color - | xor a, $ff - | st a, border_color - | goto pla_tay_pla_tax_pla_rti - | } - | } - | - | define main routine - | { - | } - = ok - -Can't `goto` a routine that outputs or trashes more than the current routine. - - | define bar routine trashes x, y, z, n { - | ld x, 200 - | ld y, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | goto bar - | } - ? IncompatibleConstraintsError - - | define bar routine outputs y trashes z, n { - | ld y, 200 - | } - | - | define main routine trashes x, z, n { - | ld x, 0 - | goto bar - | } - ? IncompatibleConstraintsError - -Can `goto` a routine that outputs or trashes less than the current routine. - - | define bar routine trashes x, z, n { - | ld x, 1 - | } - | - | define main routine trashes a, x, z, n { - | ld a, 0 - | ld x, 0 - | goto bar - | } - = ok - -Indirect goto. - - | vector routine outputs x trashes a, z, n foo - | - | define bar routine outputs x trashes a, z, n { - | ld x, 200 - | } - | - | define main routine outputs x trashes foo, a, z, n { - | copy bar, foo - | goto foo - | } - = ok - -Jumping through the vector does indeed trash, or output, the things the -vector says it does. - - | vector routine - | trashes a, x, z, n - | foo - | - | define bar routine - | trashes a, x, z, n { - | ld x, 200 - | } - | - | define sub routine - | trashes foo, a, x, z, n { - | ld x, 0 - | copy bar, foo - | goto foo - | } - | - | define main routine - | outputs a - | trashes foo, x, z, n { - | call sub - | ld a, x - | } - ? UnmeaningfulReadError: x - - | vector routine - | outputs x - | trashes a, z, n foo - | - | define bar routine - | outputs x - | trashes a, z, n { - | ld x, 200 - | } - | - | define sub routine - | outputs x - | trashes foo, a, z, n { - | ld x, 0 - | copy bar, foo - | goto foo - | } - | - | define main routine - | outputs a - | trashes foo, x, z, n { - | call sub - | ld a, x - | } - = ok - -### vector tables ### - -A vector can be copied into a vector table. - - | vector routine - | outputs x - | trashes a, z, n - | one - | vector (routine - | outputs x - | trashes a, z, n) - | table[256] many - | - | define bar routine outputs x trashes a, z, n { - | ld x, 200 - | } - | - | define main routine - | inputs one, many - | outputs one, many - | trashes a, x, n, z - | { - | ld x, 0 - | copy bar, one - | copy one, many + x - | } - = ok - -A vector can be copied out of a vector table. - - | vector routine - | outputs x - | trashes a, z, n - | one - | vector (routine - | outputs x - | trashes a, z, n) - | table[256] many - | - | define bar routine outputs x trashes a, z, n { - | ld x, 200 - | } - | - | define main routine - | inputs one, many - | outputs one, many - | trashes a, x, n, z - | { - | ld x, 0 - | copy many + x, one - | call one - | } - = ok - -A routine can be copied into a vector table. - - | vector (routine - | outputs x - | trashes a, z, n) - | table[256] many - | - | define bar routine outputs x trashes a, z, n { - | ld x, 200 - | } - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | copy bar, many + x - | } - = ok - -A vector in a vector table cannot be directly called. - - | vector (routine - | outputs x - | trashes a, z, n) - | table[256] many - | - | routine bar outputs x trashes a, z, n { - | ld x, 200 - | } - | - | define main routine - | inputs many - | outputs many - | trashes a, x, n, z - | { - | ld x, 0 - | copy bar, many + x - | call many + x - | } - ? SyntaxError - ### typedef ### A typedef is a more-readable alias for a type. "Alias" means @@ -4250,56 +805,3 @@ The new style routine definitions support typedefs. | copy foo, vec | } = ok - -### locals ### - -When memory locations are defined static to a routine, they cannot be -directly input, nor directly output; and since they are always initialized, -they cannot be trashed. Thus, they really don't participate in the analysis. - - | define foo routine - | inputs x - | outputs x - | trashes z, n - | static byte t : 0 - | { - | st x, t - | inc t - | ld x, t - | } - | - | define main routine - | trashes a, x, z, n - | static byte t : 0 - | { - | ld x, t - | 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 Compilation.md b/tests/SixtyPical Compilation.md index ec29a16..ade5ec5 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -1400,7 +1400,7 @@ Subtracting a word memory location from another word memory location. ### Tables and Pointers -Load address of table into pointer. +Associate pointer with table. Does nothing by itself. | byte table[256] tab | pointer ptr @ 254 @@ -1415,6 +1415,24 @@ Load address of table into pointer. | } | } = $080D LDY #$00 + = $080F RTS + +Reset pointer to table. + + | byte table[256] tab + | pointer ptr @ 254 + | + | define main routine + | inputs tab + | outputs tab, y + | trashes a, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | } + | } + = $080D LDY #$00 = $080F LDA #$18 = $0811 STA $FE = $0813 LDA #$08 @@ -1433,6 +1451,7 @@ Write literal through a pointer. | { | ld y, 0 | point ptr into tab { + | reset ptr 0 | copy 123, [ptr] + y | } | } @@ -1458,6 +1477,7 @@ Write stored value through a pointer. | { | ld y, 0 | point ptr into tab { + | reset ptr 0 | copy foo, [ptr] + y | } | } @@ -1483,6 +1503,7 @@ Read through a pointer, into a byte storage location, or the `a` register. | { | ld y, 0 | point ptr into tab { + | reset ptr 0 | copy [ptr] + y, foo | ld a, [ptr] + y | } @@ -1497,6 +1518,39 @@ Read through a pointer, into a byte storage location, or the `a` register. = $081C LDA ($FE),Y = $081E RTS +Multiple `reset`s may occur inside the same block. + + | byte table[256] tab @ 1024 + | pointer ptr @ 254 + | byte foo + | + | define main routine + | inputs tab + | outputs y, foo + | trashes a, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 16 + | copy [ptr] + y, foo + | reset ptr 18 + | ld a, [ptr] + y + | } + | } + = $080D LDY #$00 + = $080F LDA #$10 + = $0811 STA $FE + = $0813 LDA #$04 + = $0815 STA $FF + = $0817 LDA ($FE),Y + = $0819 STA $0827 + = $081C LDA #$12 + = $081E STA $FE + = $0820 LDA #$04 + = $0822 STA $FF + = $0824 LDA ($FE),Y + = $0826 RTS + Read and write through two pointers. | byte table[256] tab @@ -1510,7 +1564,9 @@ Read and write through two pointers. | { | ld y, 0 | point ptra into tab { + | reset ptra 0 | point ptrb into tab { + | reset ptrb 0 | copy [ptra] + y, [ptrb] + y | } | } @@ -1541,6 +1597,7 @@ Write the `a` register through a pointer. | { | ld y, 0 | point ptr into tab { + | reset ptr 0 | ld a, 255 | st a, [ptr] + y | } @@ -1571,6 +1628,7 @@ Note that this is *not* range-checked. (Yet.) | ld y, 0 | st off, c | point ptr into tab { + | reset ptr 0 | add ptr, delta | add ptr, word 1 | copy [ptr] + y, foo @@ -1619,9 +1677,9 @@ Trash does nothing except indicate that we do not care about the value anymore. = $080E LDA #$00 = $0810 RTS -### static ### +### locals ### -Memory locations defined static to a routine are allocated +Memory locations defined local static to a routine are allocated just the same as initialized global storage locations are. | define foo routine @@ -1651,3 +1709,66 @@ just the same as initialized global storage locations are. = $081D RTS = $081E .byte $FF = $081F .byte $07 + +Memory locations defined local dynamic to a routine are allocated +just the same as uninitialized global storage locations are. + + | define foo routine + | inputs x + | outputs x + | trashes z, n + | local byte t + | { + | st x, t + | inc t + | ld x, t + | } + | + | define main routine + | trashes a, x, z, n + | local byte t + | { + | ld x, 0 + | st x, t + | call foo + | } + = $080D LDX #$00 + = $080F STX $0821 + = $0812 JSR $0816 + = $0815 RTS + = $0816 STX $0820 + = $0819 INC $0820 + = $081C LDX $0820 + = $081F RTS + +Memory locations defined local dynamic to a routine are allocated +just the same as uninitialized global storage locations are, even +when declared with a fixed address. + + | define foo routine + | inputs x + | outputs x + | trashes z, n + | local byte t @ 1024 + | { + | st x, t + | inc t + | ld x, t + | } + | + | define main routine + | trashes a, x, z, n + | local byte t @ 1025 + | { + | ld x, 0 + | st x, t + | call foo + | } + = $080D LDX #$00 + = $080F STX $0401 + = $0812 JSR $0816 + = $0815 RTS + = $0816 STX $0400 + = $0819 INC $0400 + = $081C LDX $0400 + = $081F RTS diff --git a/tests/SixtyPical Control Flow.md b/tests/SixtyPical Control Flow.md new file mode 100644 index 0000000..3be659f --- /dev/null +++ b/tests/SixtyPical Control Flow.md @@ -0,0 +1,1769 @@ +SixtyPical Analysis - Control Flow +================================== + +This is a test suite, written in [Falderal][] format, for the SixtyPical +static analysis rules, with regard to flow of control. + +[Falderal]: http://catseye.tc/node/Falderal + + -> Tests for functionality "Analyze SixtyPical program" + +### call ### + +You can't call a non-routine. + + | byte up + | + | define main routine outputs x, y trashes z, n { + | ld x, 0 + | ld y, 1 + | call up + | } + ? TypeMismatchError: up + + | define main routine outputs x, y trashes z, n { + | ld x, 0 + | ld y, 1 + | call x + | } + ? TypeMismatchError: x + +Nor can you goto a non-routine. + + | byte foo + | + | define main routine { + | goto foo + | } + ? TypeMismatchError: foo + +When calling a routine, all of the locations it lists as inputs must be +initialized. + + | byte lives + | + | define foo routine + | inputs x + | trashes lives + | { + | st x, lives + | } + | + | define main routine + | { + | call foo + | } + ? UnmeaningfulReadError: x + +Note that if you call a routine that trashes a location, you also trash it. + + | byte lives + | + | define foo routine + | inputs x + | trashes lives + | { + | st x, lives + | } + | + | define main routine + | outputs x, z, n + | { + | ld x, 0 + | call foo + | } + ? ForbiddenWriteError: lives + + | byte lives + | + | define foo routine + | inputs x + | trashes lives + | { + | st x, lives + | } + | + | define main routine + | outputs x, z, n + | trashes lives + | { + | ld x, 0 + | call foo + | } + = ok + +You can't output a value that the thing you called trashed. + + | byte lives + | + | define foo routine + | inputs x + | trashes lives + | { + | st x, lives + | } + | + | define main routine + | outputs x, z, n, lives + | { + | ld x, 0 + | call foo + | } + ? UnmeaningfulOutputError: lives + +...unless you write to it yourself afterwards. + + | byte lives + | + | define foo routine + | inputs x + | trashes lives + | { + | st x, lives + | } + | + | define main routine + | outputs x, z, n, lives + | { + | ld x, 0 + | call foo + | st x, lives + | } + = ok + +If a routine declares outputs, they are initialized in the caller after +calling it. + + | define foo routine + | outputs x, z, n + | { + | ld x, 0 + | } + | + | define main routine + | outputs a + | trashes x, z, n + | { + | call foo + | ld a, x + | } + = ok + + | define foo routine + | { + | } + | + | define main routine + | outputs a + | trashes x + | { + | call foo + | ld a, x + | } + ? UnmeaningfulReadError: x + +If a routine trashes locations, they are uninitialized in the caller after +calling it. + + | define foo routine + | trashes x, z, n + | { + | ld x, 0 + | } + = ok + + | define foo routine + | trashes x, z, n + | { + | ld x, 0 + | } + | + | define main routine + | outputs a + | trashes x, z, n + | { + | call foo + | ld a, x + | } + ? UnmeaningfulReadError: x + +Calling an extern is just the same as calling a defined routine with the +same constraints. + + | define chrout routine + | inputs a + | trashes a + | @ 65490 + | + | define main routine + | trashes a, z, n + | { + | ld a, 65 + | call chrout + | } + = ok + + | define chrout routine + | inputs a + | trashes a + | @ 65490 + | + | define main routine + | trashes a, z, n + | { + | call chrout + | } + ? UnmeaningfulReadError: a + + | define chrout routine + | inputs a + | trashes a + | @ 65490 + | + | define main routine + | trashes a, x, z, n + | { + | ld a, 65 + | call chrout + | ld x, a + | } + ? UnmeaningfulReadError: a + +### if ### + +Both blocks of an `if` are analyzed. + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n, c + | { + | cmp a, 42 + | if z { + | ld x, 7 + | } else { + | ld x, 23 + | } + | } + = ok + +If a location is initialized in one block, it must be initialized in the other as well +in order to be considered to be initialized after the block. If it is not consistent, +it will be considered uninitialized. + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n, c + | { + | cmp a, 42 + | if z { + | ld x, 7 + | } else { + | ld a, 23 + | } + | } + ? UnmeaningfulOutputError: x + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n, c + | { + | cmp a, 42 + | if z { + | ld a, 6 + | } else { + | ld x, 7 + | } + | } + ? UnmeaningfulOutputError: x + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n, c + | { + | cmp a, 42 + | if not z { + | ld a, 6 + | } else { + | ld x, 7 + | } + | } + ? UnmeaningfulOutputError: x + + | define foo routine + | inputs a + | trashes a, x, z, n, c + | { + | cmp a, 42 + | if not z { + | ld a, 6 + | } else { + | ld x, 7 + | } + | ld a, x + | } + ? UnmeaningfulReadError: x + +If we don't care if it's uninitialized after the `if`, that's okay then. + + | define foo routine + | inputs a + | trashes a, x, z, n, c + | { + | cmp a, 42 + | if not z { + | ld a, 6 + | } else { + | ld x, 7 + | } + | } + = ok + +Or, if it does get initialized on both branches, that's okay then. + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n, c + | { + | cmp a, 42 + | if not z { + | ld x, 0 + | ld a, 6 + | } else { + | ld x, 7 + | } + | } + = ok + +However, this only pertains to initialization. If a value is already +initialized, either because it was set previous to the `if`, or is an +input to the routine, and it is initialized in one branch, it need not +be initialized in the other. + + | define foo routine + | outputs x + | trashes a, z, n, c + | { + | ld x, 0 + | ld a, 0 + | cmp a, 42 + | if z { + | ld x, 7 + | } else { + | ld a, 23 + | } + | } + = ok + + | define foo routine + | inputs x + | outputs x + | trashes a, z, n, c + | { + | ld a, 0 + | cmp a, 42 + | if z { + | ld x, 7 + | } else { + | ld a, 23 + | } + | } + = ok + +An `if` with a single block is analyzed as if it had an empty `else` block. + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n, c + | { + | cmp a, 42 + | if z { + | ld x, 7 + | } + | } + ? UnmeaningfulOutputError: x + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n, c + | { + | ld x, 0 + | cmp a, 42 + | if z { + | ld x, 7 + | } + | } + = ok + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n, c + | { + | ld x, 0 + | cmp a, 42 + | if not z { + | ld x, 7 + | } + | } + = ok + +The cardinal rule for trashes in an `if` is the "union rule": if one branch +trashes {`a`} and the other branch trashes {`b`} then the whole `if` statement +trashes {`a`, `b`}. + + | define foo routine + | inputs a, x, z + | trashes a, x + | { + | if z { + | trash a + | } else { + | trash x + | } + | } + = ok + + | define foo routine + | inputs a, x, z + | trashes a + | { + | if z { + | trash a + | } else { + | trash x + | } + | } + ? ForbiddenWriteError: x (in foo, line 10) + + | define foo routine + | inputs a, x, z + | trashes x + | { + | if z { + | trash a + | } else { + | trash x + | } + | } + ? ForbiddenWriteError: a (in foo, line 10) + +### repeat ### + +Repeat loop. + + | define main routine + | outputs x, y, n, z, c + | { + | ld x, 0 + | ld y, 15 + | repeat { + | inc x + | inc y + | cmp x, 10 + | } until z + | } + = ok + +You can initialize something inside the loop that was uninitialized outside. + + | define main routine + | outputs x, y, n, z, c + | { + | ld x, 0 + | repeat { + | ld y, 15 + | inc x + | cmp x, 10 + | } until z + | } + = ok + +But you can't UNinitialize something at the end of the loop that you need +initialized at the start. + + | define foo routine + | trashes y + | { + | } + | + | define main routine + | outputs x, y, n, z, c + | { + | ld x, 0 + | ld y, 15 + | repeat { + | inc x + | inc y + | call foo + | cmp x, 10 + | } until z + | } + ? UnmeaningfulReadError: y + +And if you trash the test expression (i.e. `z` in the below) inside the loop, +this is an error too. + + | word one : 0 + | word two : 0 + | + | define main routine + | inputs one, two + | outputs two + | trashes a, z, n + | { + | repeat { + | copy one, two + | } until z + | } + ? UnmeaningfulReadError: z + +The body of `repeat forever` can be empty. + + | define main routine + | { + | repeat { + | } forever + | } + = ok + +While `repeat` is most often used with `z`, it can also be used with `n`. + + | define main routine + | outputs y, n, z + | { + | ld y, 15 + | repeat { + | dec y + | } until n + | } + = ok + +### for ### + +Basic "open-faced for" loop. We'll start with the "upto" variant. + +#### upward-counting variant + +Even though we do not give the starting value in the "for" construct, +we know the exact range the loop variable takes on. + + | byte table[16] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | ld x, 0 + | for x up to 15 { + | ld a, tab + x + | } + | } + = ok + + | byte table[15] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | ld x, 0 + | for x up to 15 { + | ld a, tab + x + | } + | } + ? RangeExceededError + +You need to initialize the loop variable before the loop. + + | byte table[16] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | for x up to 15 { + | ld a, 0 + | } + | } + ? UnmeaningfulReadError + +Because routines currently do not include range constraints, +the loop variable may not be useful as an input (the location +is assumed to have the maximum range.) + + | byte table[16] tab + | + | define foo routine + | inputs tab, x + | trashes a, x, c, z, v, n { + | for x up to 15 { + | ld a, 0 + | } + | } + ? RangeExceededError + +You cannot modify the loop variable in a "for" loop. + + | byte table[16] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | ld x, 0 + | for x up to 15 { + | ld x, 0 + | } + | } + ? ForbiddenWriteError + +This includes nesting a loop on the same variable. + + | byte table[16] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | ld x, 0 + | for x up to 8 { + | for x up to 15 { + | ld a, 0 + | } + | } + | } + ? ForbiddenWriteError + +But nesting with two different variables is okay. + + | byte table[16] tab + | + | define foo routine inputs tab trashes a, x, y, c, z, v, n { + | ld x, 0 + | for x up to 8 { + | ld a, x + | ld y, a + | for y up to 15 { + | ld a, 0 + | } + | } + | } + = ok + +Inside the inner loop, the outer variable is still not writeable. + + | byte table[16] tab + | + | define foo routine inputs tab trashes a, x, y, c, z, v, n { + | ld x, 0 + | for x up to 8 { + | ld a, x + | ld y, a + | for y up to 15 { + | ld x, 0 + | } + | } + | } + ? ForbiddenWriteError + +If the range isn't known to be smaller than the final value, you can't go up to it. + + | byte table[32] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | ld x, 16 + | for x up to 15 { + | ld a, tab + x + | } + | } + ? RangeExceededError + +You can initialize something inside the loop that was uninitialized outside. + + | define main routine + | outputs x, y, n, z + | trashes c + | { + | ld x, 0 + | for x up to 15 { + | ld y, 15 + | } + | } + = ok + +But you can't UNinitialize something at the end of the loop that you need +initialized at the start of that loop. + + | define foo routine + | trashes y + | { + | } + | + | define main routine + | outputs x, y, n, z + | trashes c + | { + | ld x, 0 + | ld y, 15 + | for x up to 15 { + | inc y + | call foo + | } + | } + ? UnmeaningfulReadError: y + +The "for" loop does not preserve the `z` or `n` registers. + + | define foo routine trashes x { + | ld x, 0 + | for x up to 15 { + | } + | } + ? ForbiddenWriteError + +But it does preserve the other registers, such as `c`. + + | define foo routine trashes x, z, n { + | ld x, 0 + | for x up to 15 { + | } + | } + = ok + +In fact it does not strictly trash `z` and `n`, as they are +always set to known values after the loop. TODO: document +what these known values are! + + | define foo routine outputs z, n trashes x { + | ld x, 0 + | for x up to 15 { + | } + | } + = ok + +#### downward-counting variant + +In a "for" loop (downward-counting variant), we know the exact range the loop variable takes on. + + | byte table[16] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | ld x, 15 + | for x down to 0 { + | ld a, tab + x + | } + | } + = ok + + | byte table[15] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | ld x, 15 + | for x down to 0 { + | ld a, tab + x + | } + | } + ? RangeExceededError + +You need to initialize the loop variable before a "for" loop (downward variant). + + | byte table[16] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | for x down to 15 { + | ld a, 0 + | } + | } + ? UnmeaningfulReadError + +You cannot modify the loop variable in a "for" loop (downward variant). + + | byte table[16] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | ld x, 15 + | for x down to 0 { + | ld x, 0 + | } + | } + ? ForbiddenWriteError + +If the range isn't known to be larger than the final value, you can't go down to it. + + | byte table[32] tab + | + | define foo routine inputs tab trashes a, x, c, z, v, n { + | ld x, 0 + | for x down to 0 { + | ld a, tab + x + | } + | } + ? RangeExceededError + +The "for" loop does not preserve the `z` or `n` registers. + + | define foo routine trashes x { + | ld x, 15 + | for x down to 0 { + | } + | } + ? ForbiddenWriteError + +But it does preserve the other registers, such as `c`. + + | define foo routine trashes x, z, n { + | ld x, 15 + | for x down to 0 { + | } + | } + = ok + +In fact it does not strictly trash `z` and `n`, as they are +always set to known values after the loop. TODO: document +what these known values are! + + | define foo routine outputs z, n trashes x { + | ld x, 15 + | for x down to 0 { + | } + | } + = ok + +### routines ### + +Routines are constants. You need not, and in fact cannot, specify a constant +as an input to, an output of, or as a trashed value of a routine. + + | vector routine + | inputs x + | outputs x + | trashes z, n + | vec + | + | define foo routine + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | define main routine + | inputs foo + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + ? ConstantConstraintError: foo + + | vector routine + | inputs x + | outputs x + | trashes z, n + | vec + | + | define foo routine + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | define main routine + | outputs vec, foo + | trashes a, z, n + | { + | copy foo, vec + | } + ? ConstantConstraintError: foo + + | vector routine + | inputs x + | outputs x + | trashes z, n + | vec + | + | define foo routine + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | define main routine + | outputs vec + | trashes a, z, n, foo + | { + | copy foo, vec + | } + ? ConstantConstraintError: foo + +#### routine-vector type compatibility + +You can copy the address of a routine into a vector, if that vector type +is at least as "wide" as the type of the routine. More specifically, + +- the vector must take _at least_ the inputs that the routine takes +- the vector must produce _at least_ the outputs that the routine produces +- the vector must trash _at least_ what the routine trashes + +If the vector and the routine have the very same signature, that's not an error. + + | vector routine + | inputs x, y + | outputs x, y + | trashes z, n + | vec + | + | define foo routine + | inputs x, y + | outputs x, y + | trashes z, n + | { + | inc x + | inc y + | } + | + | define main routine + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + = ok + +If the vector takes an input that the routine doesn't take, that's not an error. +(The interface requires that a parameter be specified before calling, but the +implementation doesn't actually read it.) + + | vector routine + | inputs x, y, a + | outputs x, y + | trashes z, n + | vec + | + | define foo routine + | inputs x, y + | outputs x, y + | trashes z, n + | { + | inc x + | inc y + | } + | + | define main routine + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + = ok + +If the vector fails to take an input that the routine takes, that's an error. + + | vector routine + | inputs x + | outputs x, y + | trashes z, n + | vec + | + | define foo routine + | inputs x, y + | outputs x, y + | trashes z, n + | { + | inc x + | inc y + | } + | + | define main routine + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + ? IncompatibleConstraintsError + +If the vector produces an output that the routine doesn't produce, that's not an error. +(The interface claims the result of calling the routine is defined, but the implementation +actually preserves it instead of changing it; the caller can still treat it as a defined +output.) + + | vector routine + | inputs x, y + | outputs x, y, a + | trashes z, n + | vec + | + | define foo routine + | inputs x, y + | outputs x, y + | trashes z, n + | { + | inc x + | inc y + | } + | + | define main routine + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + = ok + +If the vector fails to produce an output that the routine produces, that's an error. + + | vector routine + | inputs x, y + | outputs x + | trashes z, n + | vec + | + | define foo routine + | inputs x, y + | outputs x, y + | trashes z, n + | { + | inc x + | inc y + | } + | + | define main routine + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + ? IncompatibleConstraintsError + +If the vector fails to trash something the routine trashes, that's an error. + + | vector routine + | inputs x, y + | outputs x, y + | trashes z + | vec + | + | define foo routine + | inputs x, y + | outputs x, y + | trashes z, n + | { + | inc x + | inc y + | } + | + | define main routine + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + ? IncompatibleConstraintsError + +If the vector trashes something the routine doesn't trash, that's not an error. +(The implementation preserves something the interface doesn't guarantee is +preserved. The caller gets no guarantee that it's preserved. It actually is, +but it doesn't know that.) + + | vector routine + | inputs x, y + | outputs x, y + | trashes a, z, n + | vec + | + | define foo routine + | inputs x, y + | outputs x, y + | trashes z, n + | { + | inc x + | inc y + | } + | + | define main routine + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + = ok + +#### other properties of routines + +Routines are read-only. + + | vector routine + | inputs x + | outputs x + | trashes z, n + | vec + | + | define foo routine + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | define main routine + | outputs vec + | trashes a, z, n + | { + | copy vec, foo + | } + ? TypeMismatchError + +Indirect call. + + | vector routine + | outputs x trashes z, n + | foo + | + | define bar routine outputs x trashes z, n { + | ld x, 200 + | } + | + | define main routine outputs x, foo trashes a, z, n { + | copy bar, foo + | call foo + | } + = ok + +Calling the vector does indeed trash the things the vector says it does. + + | vector routine trashes x, z, n foo + | + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine outputs x, foo trashes z, n { + | ld x, 0 + | copy bar, foo + | call foo + | } + ? UnmeaningfulOutputError: x + +For now at least, you cannot have a `goto` inside a `repeat` loop. + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | repeat { + | inc x + | goto bar + | } until z + | } + ? IllegalJumpError + +`goto`, as a matter of syntax, can only appear at the end +of a block; but it need not be the final instruction in a +routine. + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | goto bar + | } + = ok + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } + | } + = ok + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } + | goto bar + | } + = ok + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } + | ld x, 0 + | } + = ok + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } else { + | ld x, 0 + | goto bar + | } + | } + = ok + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } else { + | ld x, 0 + | } + | } + = ok + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } else { + | ld x, 0 + | } + | ld x, 0 + | } + = ok + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } else { + | ld x, 0 + | } + | goto bar + | } + = ok + +Even though `goto` can only appear at the end of a block, +you can still wind up with dead code; the analysis detects +this. + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } else { + | ld x, 0 + | goto bar + | } + | ld x, 100 + | } + ? TerminatedContextError + +It is important that the type context at every +`goto` is compatible with the type context at the end of +the routine. + + | define bar routine + | inputs x + | trashes x, z, n + | { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } else { + | ld x, 0 + | } + | ld x, 1 + | } + = ok + +Here, we try to trash `x` before `goto`ing a routine that inputs `x`. + + | define bar routine + | inputs x + | trashes x, z, n + | { + | ld x, 200 + | } + | + | define main routine + | outputs a + | trashes x, z, n + | { + | ld x, 0 + | if z { + | trash x + | goto bar + | } else { + | trash x + | } + | ld a, 1 + | } + ? UnmeaningfulReadError: x + +Here, we declare that main outputs `a`, but we `goto` a routine that does not output `a`. + + | define bar routine + | inputs x + | trashes x, z, n + | { + | ld x, 200 + | } + | + | define main routine + | outputs a + | trashes x, z, n + | { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } else { + | ld x, 2 + | } + | ld a, 1 + | } + ? UnmeaningfulOutputError: a + +Here, we declare that main outputs a, and we goto a routine that outputs a so that's OK. + + | define bar routine + | inputs x + | outputs a + | trashes x, z, n + | { + | ld x, 200 + | ld a, 1 + | } + | + | define main routine + | outputs a + | trashes x, z, n + | { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } else { + | ld x, 2 + | } + | ld a, 1 + | } + = ok + +Here, we declare that main outputs `a`, and we `goto` two routines, and they both output `a`. + + | define bar0 routine + | inputs x + | outputs a + | trashes x, z, n + | { + | ld a, x + | } + | + | define bar1 routine + | inputs x + | outputs a + | trashes x, z, n + | { + | ld a, 200 + | } + | + | define main routine + | outputs a + | trashes x, z, n + | { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar0 + | } else { + | ld x, 2 + | goto bar1 + | } + | } + = ok + +Here is like just above, but one routine doesn't output `a`. + + | define bar0 routine + | inputs x + | outputs a + | trashes x, z, n + | { + | ld a, x + | } + | + | define bar1 routine + | inputs x + | trashes x, z, n + | { + | ld x, 200 + | } + | + | define main routine + | outputs a + | trashes x, z, n + | { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar0 + | } else { + | ld x, 2 + | goto bar1 + | } + | } + ? InconsistentExitError + +Here is like the above, but the two routines have different inputs, and that's OK. + + | define bar0 routine + | inputs x + | outputs a + | trashes x, z, n + | { + | ld a, x + | } + | + | define bar1 routine + | outputs a + | trashes x, z, n + | { + | ld a, 200 + | } + | + | define main routine + | outputs a + | trashes x, z, n + | { + | ld x, 0 + | if z { + | ld x, 1 + | goto bar0 + | } else { + | ld x, 2 + | goto bar1 + | } + | } + = ok + +Another inconsistent exit test, this one based on "real" code +(the `ribos2` demo). + + | typedef routine + | inputs border_color, vic_intr + | outputs border_color, vic_intr + | trashes a, z, n, c + | irq_handler + | + | vector irq_handler cinv @ $314 + | vector irq_handler saved_irq_vec + | byte vic_intr @ $d019 + | byte border_color @ $d020 + | + | define pla_tay_pla_tax_pla_rti routine + | inputs a + | trashes a + | @ $EA81 + | + | define our_service_routine irq_handler + | { + | ld a, vic_intr + | st a, vic_intr + | and a, 1 + | cmp a, 1 + | if not z { + | goto saved_irq_vec + | } else { + | ld a, border_color + | xor a, $ff + | st a, border_color + | goto pla_tay_pla_tax_pla_rti + | } + | } + | + | define main routine + | { + | } + ? InconsistentExitError + + | typedef routine + | inputs border_color, vic_intr + | outputs border_color, vic_intr + | trashes a, z, n, c + | irq_handler + | + | vector irq_handler cinv @ $314 + | vector irq_handler saved_irq_vec + | byte vic_intr @ $d019 + | byte border_color @ $d020 + | + | define pla_tay_pla_tax_pla_rti routine + | inputs border_color, vic_intr + | outputs border_color, vic_intr + | trashes a, z, n, c + | @ $EA81 + | + | define our_service_routine irq_handler + | { + | ld a, vic_intr + | st a, vic_intr + | and a, 1 + | cmp a, 1 + | if not z { + | goto saved_irq_vec + | } else { + | ld a, border_color + | xor a, $ff + | st a, border_color + | goto pla_tay_pla_tax_pla_rti + | } + | } + | + | define main routine + | { + | } + = ok + +Can't `goto` a routine that outputs or trashes more than the current routine. + + | define bar routine trashes x, y, z, n { + | ld x, 200 + | ld y, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | goto bar + | } + ? IncompatibleConstraintsError + + | define bar routine outputs y trashes z, n { + | ld y, 200 + | } + | + | define main routine trashes x, z, n { + | ld x, 0 + | goto bar + | } + ? IncompatibleConstraintsError + +Can `goto` a routine that outputs or trashes less than the current routine. + + | define bar routine trashes x, z, n { + | ld x, 1 + | } + | + | define main routine trashes a, x, z, n { + | ld a, 0 + | ld x, 0 + | goto bar + | } + = ok + +Indirect goto. + + | vector routine outputs x trashes a, z, n foo + | + | define bar routine outputs x trashes a, z, n { + | ld x, 200 + | } + | + | define main routine outputs x trashes foo, a, z, n { + | copy bar, foo + | goto foo + | } + = ok + +Jumping through the vector does indeed trash, or output, the things the +vector says it does. + + | vector routine + | trashes a, x, z, n + | foo + | + | define bar routine + | trashes a, x, z, n { + | ld x, 200 + | } + | + | define sub routine + | trashes foo, a, x, z, n { + | ld x, 0 + | copy bar, foo + | goto foo + | } + | + | define main routine + | outputs a + | trashes foo, x, z, n { + | call sub + | ld a, x + | } + ? UnmeaningfulReadError: x + + | vector routine + | outputs x + | trashes a, z, n foo + | + | define bar routine + | outputs x + | trashes a, z, n { + | ld x, 200 + | } + | + | define sub routine + | outputs x + | trashes foo, a, z, n { + | ld x, 0 + | copy bar, foo + | goto foo + | } + | + | define main routine + | outputs a + | trashes foo, x, z, n { + | call sub + | ld a, x + | } + = ok + +### vector tables ### + +A vector can be copied into a vector table. + + | vector routine + | outputs x + | trashes a, z, n + | one + | vector (routine + | outputs x + | trashes a, z, n) + | table[256] many + | + | define bar routine outputs x trashes a, z, n { + | ld x, 200 + | } + | + | define main routine + | inputs one, many + | outputs one, many + | trashes a, x, n, z + | { + | ld x, 0 + | copy bar, one + | copy one, many + x + | } + = ok + +A vector can be copied out of a vector table. + + | vector routine + | outputs x + | trashes a, z, n + | one + | vector (routine + | outputs x + | trashes a, z, n) + | table[256] many + | + | define bar routine outputs x trashes a, z, n { + | ld x, 200 + | } + | + | define main routine + | inputs one, many + | outputs one, many + | trashes a, x, n, z + | { + | ld x, 0 + | copy many + x, one + | call one + | } + = ok + +A routine can be copied into a vector table. + + | vector (routine + | outputs x + | trashes a, z, n) + | table[256] many + | + | define bar routine outputs x trashes a, z, n { + | ld x, 200 + | } + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | copy bar, many + x + | } + = ok + +A vector in a vector table cannot be directly called. + + | vector (routine + | outputs x + | trashes a, z, n) + | table[256] many + | + | routine bar outputs x trashes a, z, n { + | ld x, 200 + | } + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | copy bar, many + x + | call many + x + | } + ? SyntaxError diff --git a/tests/SixtyPical Storage.md b/tests/SixtyPical Storage.md new file mode 100644 index 0000000..573551a --- /dev/null +++ b/tests/SixtyPical Storage.md @@ -0,0 +1,1922 @@ +SixtyPical Analysis - Storage +============================= + +This is a test suite, written in [Falderal][] format, for the SixtyPical +static analysis rules, with regard to storage (load, store, tables, etc.) + +[Falderal]: http://catseye.tc/node/Falderal + + -> Tests for functionality "Analyze SixtyPical program" + +### Rudiments ### + +Routines must declare their inputs, outputs, and memory locations they trash. + + | define up routine + | inputs a + | outputs a + | trashes c, z, v, n + | { + | st off, c + | add a, 1 + | } + = ok + +Routines may not declare a memory location to be both an output and trashed. + + | define main routine + | outputs a + | trashes a + | { + | ld a, 0 + | } + ? InconsistentConstraintsError: a + +If a routine declares it outputs a location, that location should be initialized. + + | define main routine + | outputs a, x, z, n + | { + | ld x, 0 + | } + ? UnmeaningfulOutputError: a + + | define main routine + | inputs a + | outputs a + | { + | } + = ok + +If a routine declares it outputs a location, that location may or may not have +been initialized. Trashing is mainly a signal to the caller. + + | define main routine + | trashes x, z, n + | { + | ld x, 0 + | } + = ok + + | define main routine + | trashes x, z, n + | { + | } + = ok + +If a routine modifies a location, it needs to either output it or trash it. + + | define main routine + | { + | ld x, 0 + | } + ? ForbiddenWriteError: x + + | define main routine + | outputs x, z, n + | { + | ld x, 0 + | } + = ok + + | define main routine + | trashes x, z, n + | { + | ld x, 0 + | } + = ok + +This is true regardless of whether it's an input or not. + + | define main routine + | inputs x + | { + | ld x, 0 + | } + ? ForbiddenWriteError: x + + | define main routine + | inputs x + | outputs x, z, n + | { + | ld x, 0 + | } + = ok + + | define main routine + | inputs x + | trashes x, z, n + | { + | ld x, 0 + | } + = ok + +If a routine trashes a location, this must be declared. + + | define foo routine + | trashes x + | { + | trash x + | } + = ok + + | define foo routine + | { + | trash x + | } + ? ForbiddenWriteError: x + + | define foo routine + | outputs x + | { + | trash x + | } + ? UnmeaningfulOutputError: x + +If a routine causes a location to be trashed, this must be declared in the caller. + + | define trash_x routine + | trashes x, z, n + | { + | ld x, 0 + | } + | + | define foo routine + | trashes x, z, n + | { + | call trash_x + | } + = ok + + | define trash_x routine + | trashes x, z, n + | { + | ld x, 0 + | } + | + | define foo routine + | trashes z, n + | { + | call trash_x + | } + ? ForbiddenWriteError: x + + | define trash_x routine + | trashes x, z, n + | { + | ld x, 0 + | } + | + | define foo routine + | outputs x + | trashes z, n + | { + | call trash_x + | } + ? UnmeaningfulOutputError: x (in foo, line 12) + +If a routine reads or writes a user-define memory location, it needs to declare that too. + + | byte b1 @ 60000 + | byte b2 : 3 + | word w1 @ 60001 + | word w2 : 2000 + | + | define main routine + | inputs b1, w1 + | outputs b2, w2 + | trashes a, z, n + | { + | ld a, b1 + | st a, b2 + | copy w1, w2 + | } + = ok + +### ld ### + +Can't `ld` from a memory location that isn't initialized. + + | define main routine + | inputs a, x + | trashes a, z, n + | { + | ld a, x + | } + = ok + + | define main routine + | inputs a + | trashes a + | { + | ld a, x + | } + ? UnmeaningfulReadError: x + +Can't `ld` to a memory location that doesn't appear in (outputs ∪ trashes). + + | define main routine + | trashes a, z, n + | { + | ld a, 0 + | } + = ok + + | define main routine + | outputs a + | trashes z, n + | { + | ld a, 0 + | } + = ok + + | define main routine + | outputs z, n + | trashes a + | { + | ld a, 0 + | } + = ok + + | define main routine + | trashes z, n + | { + | ld a, 0 + | } + ? ForbiddenWriteError: a + + | define main routine + | trashes a, n + | { + | ld a, 0 + | } + ? ForbiddenWriteError: z + +Can't `ld` a `word` type. + + | word foo + | + | define main routine + | inputs foo + | trashes a, n, z + | { + | ld a, foo + | } + ? TypeMismatchError: foo and a + +### st ### + +Can't `st` from a memory location that isn't initialized. + + | byte lives + | define main routine + | inputs x + | trashes lives + | { + | st x, lives + | } + = ok + + | byte lives + | define main routine + | trashes x, lives + | { + | st x, lives + | } + ? UnmeaningfulReadError: x + +Can't `st` to a memory location that doesn't appear in (outputs ∪ trashes). + + | byte lives + | define main routine + | trashes lives + | { + | st 0, lives + | } + = ok + + | byte lives + | define main routine + | outputs lives + | { + | st 0, lives + | } + = ok + + | byte lives + | define main routine + | inputs lives + | { + | st 0, lives + | } + ? ForbiddenWriteError: lives + +Can't `st` a `word` type. + + | word foo + | + | define main routine + | outputs foo + | trashes a, n, z + | { + | ld a, 0 + | st a, foo + | } + ? TypeMismatchError + +### tables ### + +Storing to a table, you must use an index. + + | byte one + | byte table[256] many + | + | define main routine + | outputs one + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, 0 + | st a, one + | } + = ok + + | byte one + | byte table[256] many + | + | define main routine + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, 0 + | st a, many + | } + ? TypeMismatchError + + | byte one + | byte table[256] many + | + | define main routine + | outputs one + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, 0 + | st a, one + x + | } + ? TypeMismatchError + + | byte one + | byte table[256] many + | + | define main routine + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, 0 + | st a, many + x + | } + = ok + +The index must be initialized. + + | byte one + | byte table[256] many + | + | define main routine + | outputs many + | trashes a, x, n, z + | { + | ld a, 0 + | st a, many + x + | } + ? UnmeaningfulReadError: x + +Reading from a table, you must use an index. + + | byte one + | + | define main routine + | outputs one + | trashes a, x, n, z + | { + | ld x, 0 + | st x, one + | ld a, one + | } + = ok + + | byte one + | + | define main routine + | outputs one + | trashes a, x, n, z + | { + | ld x, 0 + | st x, one + | ld a, one + x + | } + ? TypeMismatchError + + | byte table[256] many + | + | define main routine + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, 0 + | st a, many + x + | ld a, many + | } + ? TypeMismatchError + + | byte table[256] many + | + | define main routine + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, 0 + | st a, many + x + | ld a, many + x + | } + = ok + + | byte table[256] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, many + x + | } + = ok + +The index must be initialized. + + | byte table[256] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld a, many + x + | } + ? UnmeaningfulReadError: x + +Storing to a table, you may also include a constant offset. + + | byte one + | byte table[256] many + | + | define main routine + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, 0 + | st a, many + 100 + x + | } + = ok + +Reading from a table, you may also include a constant offset. + + | byte table[256] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, many + 100 + x + | } + = ok + +Using a constant offset, you can read and write entries in +the table beyond the 256th. + + | byte one + | byte table[1024] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | ld a, many + 999 + x + | st a, many + 1000 + x + | } + = ok + +There are other operations you can do on tables. (1/3) + + | byte table[256] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, c, n, z, v + | { + | ld x, 0 + | ld a, 0 + | st off, c + | add a, many + x + | sub a, many + x + | cmp a, many + x + | } + = ok + +There are other operations you can do on tables. (2/3) + + | byte table[256] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, c, n, z + | { + | ld x, 0 + | ld a, 0 + | and a, many + x + | or a, many + x + | xor a, many + x + | } + = ok + +There are other operations you can do on tables. (3/3) + + | byte table[256] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, c, n, z + | { + | ld x, 0 + | ld a, 0 + | st off, c + | shl many + x + | shr many + x + | inc many + x + | dec many + x + | } + = ok + +Copying to and from a word table. + + | word one + | word table[256] many + | + | define main routine + | inputs one, many + | outputs one, many + | trashes a, x, n, z + | { + | ld x, 0 + | copy one, many + x + | copy many + x, one + | } + = ok + + | word one + | word table[256] many + | + | define main routine + | inputs one, many + | outputs one, many + | trashes a, x, n, z + | { + | ld x, 0 + | copy one, many + | } + ? TypeMismatchError + + | word one + | word table[256] many + | + | define main routine + | inputs one, many + | outputs one, many + | trashes a, x, n, z + | { + | ld x, 0 + | copy one + x, many + | } + ? TypeMismatchError + +You can also copy a literal word to a word table. +(Even if the table has fewer than 256 entries.) + + | word table[32] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | copy 9999, many + x + | } + = ok + +Copying to and from a word table with a constant offset. + + | word one + | word table[256] many + | + | define main routine + | inputs one, many + | outputs one, many + | trashes a, x, n, z + | { + | ld x, 0 + | copy one, many + 100 + x + | copy many + 100 + x, one + | copy 9999, many + 1 + x + | } + = ok + +#### tables: range checking #### + +It is a static analysis error if it cannot be proven that a read or write +to a table falls within the defined size of that table. + +If a table has 256 entries, then there is never a problem (so long as +no constant offset is supplied), because a byte cannot index any entry +outside of 0..255. + +But if the table has fewer than 256 entries, or if a constant offset is +supplied, there is the possibility that the index will refer to an +entry in the table which does not exist. + +A SixtyPical implementation must be able to prove that the index is inside +the range of the table in various ways. The simplest is to show that a +constant value falls inside or outside the range of the table. + + | byte table[32] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 31 + | ld a, many + x + | st a, many + x + | } + = ok + + | byte table[32] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 32 + | ld a, many + x + | } + ? RangeExceededError + + | byte table[32] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 32 + | ld a, 0 + | st a, many + x + | } + ? RangeExceededError + +Any constant offset is taken into account in this check. + + | byte table[32] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 31 + | ld a, many + 1 + x + | } + ? RangeExceededError + + | byte table[32] many + | + | define main routine + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 31 + | ld a, 0 + | st a, many + 1 + x + | } + ? RangeExceededError + +This applies to `copy` as well. + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs many, one + | outputs many, one + | trashes a, x, n, z + | { + | ld x, 31 + | copy one, many + x + | copy many + x, one + | } + = ok + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs many, one + | outputs many, one + | trashes a, x, n, z + | { + | ld x, 32 + | copy many + x, one + | } + ? RangeExceededError + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs many, one + | outputs many, one + | trashes a, x, n, z + | { + | ld x, 32 + | copy one, many + x + | } + ? RangeExceededError + +Any constant offset is taken into account in this check. + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs many, one + | outputs many, one + | trashes a, x, n, z + | { + | ld x, 31 + | copy many + 1 + x, one + | } + ? RangeExceededError + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs many, one + | outputs many, one + | trashes a, x, n, z + | { + | ld x, 31 + | copy one, many + 1 + x + | } + ? RangeExceededError + +`AND`'ing a register with a value ensures the range of the +register will not exceed the range of the value. This can +be used to "clip" the range of an index so that it fits in +a table. + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 31 + | ld x, a + | copy one, many + x + | copy many + x, one + | } + = ok + +Tests for "clipping", but not enough. + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 63 + | ld x, a + | copy one, many + x + | copy many + x, one + | } + ? RangeExceededError + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 31 + | ld x, a + | copy one, many + 1 + x + | copy many + 1 + x, one + | } + ? RangeExceededError + +If you alter the value after "clipping" it, the range can +no longer be guaranteed. + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 31 + | ld x, a + | inc x + | copy one, many + x + | copy many + x, one + | } + ? RangeExceededError + +When the range of a location is known, incrementing or +decrementing that location's value will shift the known +range. It will not invalidate it unless the known range +is at the limits of the possible ranges for the type. + + | vector routine + | trashes a, z, n + | print + | + | vector (routine + | trashes a, z, n) + | table[32] vectors + | + | define main routine + | inputs vectors, print + | outputs vectors + | trashes print, a, x, z, n, c + | { + | ld x, 0 + | inc x + | copy print, vectors + x + | } + = ok + + | vector routine + | trashes a, z, n + | print + | + | vector (routine + | trashes a, z, n) + | table[32] vectors + | + | define main routine + | inputs vectors, print + | outputs vectors + | trashes print, a, x, z, n, c + | { + | ld x, 32 + | dec x + | copy print, vectors + x + | } + = ok + +### trash ### + +Trash does nothing except indicate that we do not care about the value anymore. + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n + | { + | st a, x + | ld a, 0 + | trash a + | } + = ok + + | define foo routine + | inputs a + | outputs a, x + | trashes z, n + | { + | st a, x + | ld a, 0 + | trash a + | } + ? UnmeaningfulOutputError: a + + | define foo routine + | inputs a + | outputs x + | trashes a, z, n + | { + | st a, x + | trash a + | st a, x + | } + ? UnmeaningfulReadError: a + +### copy ### + +Can't `copy` from a memory location that isn't initialized. + + | byte lives + | define main routine + | inputs x + | outputs lives + | trashes a, z, n + | { + | copy x, lives + | } + = ok + + | byte lives + | define main routine + | outputs lives + | trashes x, a, z, n + | { + | copy x, lives + | } + ? UnmeaningfulReadError: x + +Can't `copy` to a memory location that doesn't appear in (outputs ∪ trashes). + + | byte lives + | define main routine + | trashes lives, a, z, n + | { + | copy 0, lives + | } + = ok + + | byte lives + | define main routine + | outputs lives + | trashes a, z, n + | { + | copy 0, lives + | } + = ok + + | byte lives + | define main routine + | inputs lives + | trashes a, z, n + | { + | copy 0, lives + | } + ? ForbiddenWriteError: lives + +a, z, and n are trashed, and must be declared as such. + +(Note, both n and z are forbidden writes in this test.) + + | byte lives + | define main routine + | outputs lives + | { + | copy 0, lives + | } + ? ForbiddenWriteError + +a, z, and n are trashed, and must not be declared as outputs. + +(Note, both n and a are unmeaningful outputs in this test.) + + | byte lives + | define main routine + | outputs lives, a, z, n + | { + | copy 0, lives + | } + ? UnmeaningfulOutputError + +Unless of course you subsequently initialize them. + + | byte lives + | define main routine + | outputs lives, a, z, n + | { + | copy 0, lives + | ld a, 0 + | } + = ok + +Can `copy` from a `byte` to a `byte`. + + | byte source : 0 + | byte dest + | + | define main routine + | inputs source + | outputs dest + | trashes a, z, n + | { + | copy source, dest + | } + = ok + +The understanding is that, because `copy` trashes `a`, `a` cannot be used +as the destination of a `copy`. + + | byte source : 0 + | byte dest + | + | define main routine + | inputs source + | outputs dest + | trashes a, z, n + | { + | copy source, a + | } + ? ForbiddenWriteError + +Can `copy` from a `word` to a `word`. + + | word source : 0 + | word dest + | + | define main routine + | inputs source + | outputs dest + | trashes a, z, n + | { + | copy source, dest + | } + = ok + +Can't `copy` from a `byte` to a `word`. + + | byte source : 0 + | word dest + | + | define main routine + | inputs source + | outputs dest + | trashes a, z, n + | { + | copy source, dest + | } + ? TypeMismatchError + +Can't `copy` from a `word` to a `byte`. + + | word source : 0 + | byte dest + | + | define main routine + | inputs source + | outputs dest + | trashes a, z, n + | { + | copy source, dest + | } + ? TypeMismatchError + +### point ... into blocks ### + +Pointer must be a pointer type. + + | byte table[256] tab + | word ptr + | + | define main routine + | inputs tab + | outputs y, tab + | trashes a, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy 123, [ptr] + y + | } + | } + ? TypeMismatchError + +Cannot write through pointer outside a `point ... into` block. + + | byte table[256] tab + | pointer ptr + | + | define main routine + | inputs tab, ptr + | outputs y, tab + | trashes a, z, n, ptr + | { + | ld y, 0 + | copy 123, [ptr] + y + | } + ? ForbiddenWriteError + +After a `point ... into` block, the pointer is no longer meaningful. + + | byte table[256] tab + | pointer ptr + | + | define main routine + | inputs tab + | outputs y, tab + | trashes a, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy 123, [ptr] + y + | } + | copy 123, [ptr] + y + | } + ? UnmeaningfulReadError: ptr + +Write literal through a pointer into a table. + + | byte table[256] tab + | pointer ptr + | + | define main routine + | inputs tab + | outputs y, tab + | trashes a, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy 123, [ptr] + y + | } + | } + = ok + +Writing into a table via a pointer does use `y`. + + | byte table[256] tab + | pointer ptr + | + | define main routine + | inputs tab + | outputs tab + | trashes a, z, n, ptr + | { + | point ptr into tab { + | reset ptr 0 + | copy 123, [ptr] + y + | } + | } + ? UnmeaningfulReadError + +Write stored value through a pointer into a table. + + | byte table[256] tab + | pointer ptr + | byte foo + | + | define main routine + | inputs foo, tab + | outputs y, tab + | trashes a, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy foo, [ptr] + y + | } + | } + = ok + +Read a table entry via a pointer. + + | byte table[256] tab + | pointer ptr + | byte foo + | + | define main routine + | inputs tab + | outputs foo + | trashes a, y, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy [ptr] + y, foo + | } + | } + = ok + +Read and write through two pointers into a table. + + | byte table[256] tab + | pointer ptra + | pointer ptrb + | + | define main routine + | inputs tab + | outputs tab + | trashes a, y, z, n, ptra, ptrb + | { + | ld y, 0 + | point ptra into tab { + | reset ptra 0 + | point ptrb into tab { + | reset ptrb 0 + | copy [ptra] + y, [ptrb] + y + | } + | } + | } + = ok + +Read through a pointer into a table, to the `a` register. Note that this is done with `ld`, +not `copy`. + + | byte table[256] tab + | pointer ptr + | byte foo + | + | define main routine + | inputs tab + | outputs a + | trashes y, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | ld a, [ptr] + y + | } + | } + = ok + +Write the `a` register through a pointer into a table. Note that this is done with `st`, +not `copy`. + + | byte table[256] tab + | pointer ptr + | byte foo + | + | define main routine + | inputs tab + | outputs tab + | trashes a, y, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | ld a, 255 + | st a, [ptr] + y + | } + | } + = ok + +Cannot get a pointer into a non-byte (for instance, word) table. + + | word table[256] tab + | pointer ptr + | byte foo + | + | define main routine + | inputs tab + | outputs foo + | trashes a, y, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy [ptr] + y, foo + | } + | } + ? TypeMismatchError + +Cannot get a pointer into a non-byte (for instance, vector) table. + + | vector (routine trashes a, z, n) table[256] tab + | pointer ptr + | vector (routine trashes a, z, n) foo + | + | define main routine + | inputs tab + | outputs foo + | trashes a, y, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy [ptr] + y, foo + | } + | } + ? TypeMismatchError + +`point into` by itself only requires `ptr` to be writeable. By itself, +it does not require `tab` to be readable or writeable. + + | byte table[256] tab + | pointer ptr + | + | define main routine + | trashes a, z, n, ptr + | { + | point ptr into tab { + | ld a, 0 + | } + | } + = ok + + | byte table[256] tab + | pointer ptr + | + | define main routine + | trashes a, z, n + | { + | point ptr into tab { + | ld a, 0 + | } + | } + = ok + +It does need to be accessible by the routine if you're going to reset the pointer +to it though. + + | byte table[256] tab + | pointer ptr + | + | define main routine + | trashes a, z, n + | { + | point ptr into tab { + | reset ptr 0 + | ld a, 0 + | } + | } + ? UnmeaningfulReadError: tab + +And the pointer needs to be writeable if it's going to be reset as well. + + | byte table[256] tab + | pointer ptr + | + | define main routine + | inputs tab + | trashes a, z, n + | { + | point ptr into tab { + | reset ptr 0 + | ld a, 0 + | } + | } + ? ForbiddenWriteError + +After a `point into` block, the pointer is no longer meaningful and cannot +be considered an output of the routine. + + | byte table[256] tab + | pointer ptr + | + | define main routine + | inputs tab + | outputs y, tab, ptr + | trashes a, z, n + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy 123, [ptr] + y + | } + | } + ? UnmeaningfulOutputError + +If code in a routine reads from a table through a pointer, the table must be in +the `inputs` of that routine. + + | byte table[256] tab + | pointer ptr + | byte foo + | + | define main routine + | outputs foo + | trashes a, y, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy [ptr] + y, foo + | } + | } + ? UnmeaningfulReadError + +Likewise, if code in a routine writes into a table via a pointer, the table must +be in the `outputs` of that routine. + + | byte table[256] tab + | pointer ptr + | + | define main routine + | inputs tab + | trashes a, y, z, n, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy 123, [ptr] + y + | } + | } + ? ForbiddenWriteError + +If code in a routine reads from a table through a pointer, the pointer *should* +remain inside the range of the table. This is currently not checked. + + | byte table[32] tab + | pointer ptr + | byte foo + | + | define main routine + | inputs tab + | outputs foo + | trashes a, y, c, z, n, v, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | st off, c + | add ptr, word 100 + | copy [ptr] + y, foo + | } + | } + = ok + +Likewise, if code in a routine writes into a table through a pointer, the pointer +*should* remain inside the range of the table. This is currently not checked. + + | byte table[32] tab + | pointer ptr + | + | define main routine + | inputs tab + | outputs tab + | trashes a, y, c, z, n, v, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | st off, c + | add ptr, word 100 + | copy 123, [ptr] + y + | } + | } + = ok + +### reset ### + +Can't have a `reset` outside a `point ... into` block. + + | byte table[32] tab + | pointer ptr + | + | define main routine + | inputs tab + | outputs tab + | trashes a, y, c, z, n, v, ptr + | { + | ld y, 0 + | reset ptr 0 + | point ptr into tab { + | st off, c + | add ptr, word 10 + | copy 123, [ptr] + y + | } + | } + ? ForbiddenWriteError: ptr + +Can't write into a table if the pointer hasn't been `reset`; the +pointer is not meaningful until it's `reset`. + + | byte table[32] tab + | pointer ptr + | + | define main routine + | inputs tab + | outputs tab + | trashes a, y, c, z, n, v, ptr + | { + | ld y, 0 + | point ptr into tab { + | st off, c + | copy 123, [ptr] + y + | } + | } + ? UnmeaningfulReadError: ptr + +Can't read from a table if the pointer hasn't been reset. + + | byte table[32] tab + | pointer ptr + | byte ou + | + | define main routine + | inputs tab + | outputs tab, ou + | trashes a, y, c, z, n, v, ptr + | { + | ld y, 0 + | point ptr into tab { + | st off, c + | copy [ptr] + y, ou + | } + | } + ? UnmeaningfulReadError: ptr + +Multiple `reset`s may occur inside the same block. + + | byte table[32] tab + | pointer ptr + | + | define main routine + | inputs tab + | outputs tab + | trashes a, y, c, z, n, v, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 10 + | copy 123, [ptr] + y + | reset ptr 20 + | copy 35, [ptr] + y + | } + | } + = ok + +The offset in `reset` may not exceed the table's size. + + | byte table[32] tab + | pointer ptr + | + | define main routine + | inputs tab + | outputs tab + | trashes a, y, c, z, n, v, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 32 + | copy 123, [ptr] + y + | } + | } + ? RangeExceededError + +### locals ### + +When memory locations are defined static to a routine, they cannot be +directly input, nor directly output; and since they are always initialized, +they cannot be trashed. Thus, they really don't participate in the analysis. + + | define foo routine + | inputs x + | outputs x + | trashes z, n + | static byte t : 0 + | { + | st x, t + | inc t + | ld x, t + | } + | + | define main routine + | trashes a, x, z, n + | static byte t : 0 + | { + | ld x, t + | 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 + +Local non-statics can be meaningully given an explicit address. + + | define main routine + | inputs x + | outputs x + | trashes z, n + | local byte t @ 1024 + | { + | st x, t + | inc t + | ld x, t + | } + = ok + + | define main routine + | outputs x + | trashes z, n + | local byte t @ 1024 + | { + | inc t + | ld x, t + | } + ? UnmeaningfulReadError: t + +### save ### + +Basic neutral test, where the `save` makes no difference. + + | define main routine + | inputs a, x + | outputs a, x + | trashes z, n + | { + | ld a, 1 + | save x { + | ld a, 2 + | } + | ld a, 3 + | } + = ok + +Saving any location (other than `a`) will trash `a`. + + | define main routine + | inputs a, x + | outputs a, x + | trashes z, n + | { + | ld a, 1 + | save x { + | ld a, 2 + | } + | } + ? UnmeaningfulOutputError + +Saving `a` does not trash anything. + + | define main routine + | inputs a, x + | outputs a, x + | trashes z, n + | { + | ld x, 1 + | save a { + | ld x, 2 + | } + | ld x, 3 + | } + = ok + +A defined value that has been saved can be trashed inside the block. +It will continue to be defined outside the block. + + | define main routine + | outputs x, y + | trashes a, z, n + | { + | ld x, 0 + | save x { + | ld y, 0 + | trash x + | } + | } + = ok + +A trashed value that has been saved can be used inside the block. +It will continue to be trashed outside the block. + +(Note, both x and a are unmeaningful in this test.) + + | define main routine + | inputs a + | outputs a, x + | trashes z, n + | { + | ld x, 0 + | trash x + | save x { + | ld a, 0 + | ld x, 1 + | } + | } + ? UnmeaningfulOutputError + +The known range of a value will be preserved outside the block as well. + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 31 + | ld x, a + | save x { + | ld x, 255 + | } + | copy one, many + x + | copy many + x, one + | } + = ok + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 63 + | ld x, a + | save x { + | ld x, 1 + | } + | copy one, many + x + | copy many + x, one + | } + ? RangeExceededError + +The known properties of a value are preserved inside the block, too. + + | word one: 77 + | word table[32] many + | + | define main routine + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 31 + | ld x, a + | save x { + | copy one, many + x + | copy many + x, one + | } + | copy one, many + x + | copy many + x, one + | } + = ok + +A value which is not output from the routine, is preserved by the +routine; and can appear in a `save` exactly because a `save` preserves it. + + | define main routine + | outputs y + | trashes a, z, n + | { + | save x { + | ld y, 0 + | ld x, 1 + | } + | } + = ok + +Because saving anything except `a` trashes `a`, a common idiom is to save `a` +first in a nested series of `save`s. + + | define main routine + | inputs a + | outputs a + | trashes z, n + | { + | save a { + | save x { + | ld a, 0 + | ld x, 1 + | } + | } + | } + = ok + +There is a shortcut syntax for a nested series of `save`s. + + | define main routine + | inputs a + | outputs a + | trashes z, n + | { + | save a, x { + | ld a, 0 + | ld x, 1 + | } + | } + = ok + +`a` is only preserved if it is the outermost thing `save`d. + + | define main routine + | inputs a + | outputs a + | trashes z, n + | { + | save x, a { + | ld a, 0 + | ld x, 1 + | } + | } + ? UnmeaningfulOutputError: a + +Not just registers, but also user-defined locations can be saved. + + | byte foo + | + | define main routine + | trashes a, z, n + | { + | save foo { + | st 5, foo + | } + | } + = ok + +But only if they are bytes. + + | word foo + | + | define main routine + | trashes a, z, n + | { + | save foo { + | copy 555, foo + | } + | } + ? TypeMismatchError + + | byte table[16] tab + | + | define main routine + | trashes a, y, z, n + | { + | save tab { + | ld y, 0 + | st 5, tab + y + | } + | } + ? TypeMismatchError + +A `goto` cannot appear within a `save` block. + + | define other routine + | trashes a, z, n + | { + | ld a, 0 + | } + | + | define main routine + | trashes a, z, n + | { + | ld a, 1 + | save x { + | ld x, 2 + | goto other + | } + | } + ? IllegalJumpError diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index a40a99a..c4458ea 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -176,6 +176,7 @@ Other blocks. | ld a, 0 | } | point ptr into tab { + | reset ptr 0 | ld a, [ptr] + y | } | } @@ -681,6 +682,7 @@ Tables and pointers. | | define main routine { | point ptr into buf { + | reset ptr 0 | copy 123, [ptr] + y | copy [ptr] + y, foo | copy [ptr] + y, [ptrb] + y @@ -784,6 +786,20 @@ Local static memory locations must always be given an initial value. | } ? SyntaxError +Local static memory locations may not be given an address. + + | define main routine + | inputs x + | outputs x + | trashes z, n + | static byte t @ 1024 + | { + | st x, t + | inc t + | ld x, t + | } + ? SyntaxError + Local dynamic memory locations may not be given an initial value. | define main routine @@ -798,6 +814,20 @@ Local dynamic memory locations may not be given an initial value. | } ? SyntaxError +Local dynamic memory locations may be given an address. + + | define main routine + | inputs x + | outputs x + | trashes z, n + | local byte t @ 1024 + | { + | st x, t + | inc t + | ld x, t + | } + = ok + Name of a local cannot shadow an existing global or local. | byte t