From b5763e84b46d4f629cbd032c171961a2eb38e563 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Thu, 22 Oct 2015 20:01:02 +0100 Subject: [PATCH] Constants (such as routines) cannot be given in call-constraints. --- README.markdown | 2 +- src/sixtypical/analyzer.py | 63 +++++++++++++++++---------- src/sixtypical/model.py | 12 +++++- tests/SixtyPical Analysis.md | 83 +++++++++++++++++++++++++++++++----- 4 files changed, 125 insertions(+), 35 deletions(-) diff --git a/README.markdown b/README.markdown index 3e874d6..a2326b5 100644 --- a/README.markdown +++ b/README.markdown @@ -44,11 +44,11 @@ TODO For 0.6: -* routines shouldn't need to be listed as inputs. * A more involved demo for the C64 — one that sets up an interrupt. For 0.7: +* always analyze before executing or compiling, unless told not to * `word` type. * `word table` type. * `trash` instruction. diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index c903a0d..b5f5c71 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -29,19 +29,27 @@ class ForbiddenWriteError(StaticAnalysisError): pass -class InconsistentConstraintsError(StaticAnalysisError): - pass - - class TypeMismatchError(StaticAnalysisError): pass -class IncompatibleConstraintsError(StaticAnalysisError): +class IllegalJumpError(StaticAnalysisError): pass -class IllegalJumpError(StaticAnalysisError): +class ConstraintsError(StaticAnalysisError): + pass + + +class ConstantConstraintError(ConstraintsError): + pass + + +class InconsistentConstraintsError(ConstraintsError): + pass + + +class IncompatibleConstraintsError(ConstraintsError): pass @@ -57,31 +65,39 @@ class Context(object): A location is writeable if it was listed in the outputs and trashes lists of this routine. """ - def __init__(self, routine, inputs, outputs, trashes): + def __init__(self, routines, routine, inputs, outputs, trashes): + self.routines = routines # Location -> AST node self.routine = routine self._touched = set() self._meaningful = set() self._writeable = set() for ref in inputs: + if ref.is_constant(): + raise ConstantConstraintError('%s in %s' % (ref.name, routine.name)) self._meaningful.add(ref) output_names = set() for ref in outputs: + if ref.is_constant(): + raise ConstantConstraintError('%s in %s' % (ref.name, routine.name)) output_names.add(ref.name) self._writeable.add(ref) for ref in trashes: + if ref.is_constant(): + raise ConstantConstraintError('%s in %s' % (ref.name, routine.name)) if ref.name in output_names: - raise InconsistentConstraintsError(ref.name) + raise InconsistentConstraintsError('%s in %s' % (ref.name, routine.name)) self._writeable.add(ref) def clone(self): - c = Context(self.routine, [], [], []) + c = Context(self.routines, self.routine, [], [], []) c._touched = set(self._touched) c._meaningful = set(self._meaningful) c._writeable = set(self._writeable) return c def set_from(self, c): + assert c.routines == self.routines assert c.routine == self.routine self._touched = set(c._touched) self._meaningful = set(c._meaningful) @@ -98,7 +114,7 @@ class Context(object): def assert_meaningful(self, *refs, **kwargs): exception_class = kwargs.get('exception_class', UnmeaningfulReadError) for ref in refs: - if isinstance(ref, ConstantRef): + if isinstance(ref, ConstantRef) or ref in self.routines: pass elif isinstance(ref, LocationRef): if ref not in self._meaningful: @@ -141,14 +157,15 @@ class Analyzer(object): def __init__(self): self.current_routine = None self.has_encountered_goto = False + self.routines = {} def analyze_program(self, program): assert isinstance(program, Program) - routines = {r.name: r for r in program.routines} + self.routines = {r.location: r for r in program.routines} for routine in program.routines: - self.analyze_routine(routine, routines) + self.analyze_routine(routine) - def analyze_routine(self, routine, routines): + def analyze_routine(self, routine): assert isinstance(routine, Routine) self.current_routine = routine self.has_encountered_goto = False @@ -156,8 +173,8 @@ class Analyzer(object): # it's an extern, that's fine return type = routine.location.type - context = Context(routine, type.inputs, type.outputs, type.trashes) - self.analyze_block(routine.block, context, routines) + context = Context(self.routines, routine, type.inputs, type.outputs, type.trashes) + self.analyze_block(routine.block, context) if not self.has_encountered_goto: for ref in type.outputs: context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError) @@ -167,14 +184,14 @@ class Analyzer(object): raise ForbiddenWriteError(message) self.current_routine = None - def analyze_block(self, block, context, routines): + def analyze_block(self, block, context): assert isinstance(block, Block) for i in block.instrs: if self.has_encountered_goto: raise IllegalJumpError(i) - self.analyze_instr(i, context, routines) + self.analyze_instr(i, context) - def analyze_instr(self, instr, context, routines): + def analyze_instr(self, instr, context): assert isinstance(instr, Instr) opcode = instr.opcode dest = instr.dest @@ -228,9 +245,9 @@ class Analyzer(object): elif opcode == 'if': context1 = context.clone() context2 = context.clone() - self.analyze_block(instr.block1, context1, routines) + self.analyze_block(instr.block1, context1) if instr.block2 is not None: - self.analyze_block(instr.block2, context2, routines) + self.analyze_block(instr.block2, context2) # TODO may we need to deal with touched separately here too? # probably not; if it wasn't meaningful in the first place, it # doesn't really matter if you modified it or not, coming out. @@ -242,11 +259,11 @@ class Analyzer(object): elif opcode == 'repeat': # it will always be executed at least once, so analyze it having # been executed the first time. - self.analyze_block(instr.block, context, routines) + self.analyze_block(instr.block, context) # now analyze it having been executed a second time, with the context # of it having already been executed. - self.analyze_block(instr.block, context, routines) + self.analyze_block(instr.block, context) # NB I *think* that's enough... but it might not be? elif opcode == 'copy': @@ -275,7 +292,7 @@ class Analyzer(object): context.set_touched(REG_A, FLAG_Z, FLAG_N) context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N) elif opcode == 'with-sei': - self.analyze_block(instr.block, context, routines) + self.analyze_block(instr.block, context) elif opcode == 'goto': location = instr.location type = location.type diff --git a/src/sixtypical/model.py b/src/sixtypical/model.py index 2e344fc..929252b 100644 --- a/src/sixtypical/model.py +++ b/src/sixtypical/model.py @@ -57,7 +57,11 @@ class VectorType(ExecutableType): class Ref(object): - pass + def is_constant(self): + """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.""" + raise NotImplementedError class LocationRef(Ref): @@ -80,6 +84,9 @@ class LocationRef(Ref): def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name) + def is_constant(self): + return isinstance(self.type, RoutineType) + class ConstantRef(Ref): def __init__(self, type, value): @@ -97,6 +104,9 @@ class ConstantRef(Ref): def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.value) + def is_constant(self): + return True + REG_A = LocationRef(TYPE_BYTE, 'a') REG_X = LocationRef(TYPE_BYTE, 'x') diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 9c918e2..3c73522 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1048,7 +1048,8 @@ Unless of course you subsequently initialize them. | } = ok -You can copy the address of a routine into a vector, if that vector is declared appropriately. +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 vec | inputs x @@ -1070,6 +1071,72 @@ You can copy the address of a routine into a vector, if that vector is declared | { | copy foo, vec | } + ? ConstantConstraintError: foo in main + + | vector vec + | inputs x + | outputs x + | trashes z, n + | + | routine foo + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | routine main + | outputs vec, foo + | trashes a, z, n + | { + | copy foo, vec + | } + ? ConstantConstraintError: foo in main + + | vector vec + | inputs x + | outputs x + | trashes z, n + | + | routine foo + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | routine main + | outputs vec + | trashes a, z, n, foo + | { + | copy foo, vec + | } + ? ConstantConstraintError: foo in main + +You can copy the address of a routine into a vector, if that vector is +declared appropriately. + + | vector vec + | inputs x + | outputs x + | trashes z, n + | + | routine foo + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | routine main + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } = ok But not if the vector is declared inappropriately. @@ -1088,7 +1155,6 @@ But not if the vector is declared inappropriately. | } | | routine main - | inputs foo | outputs vec | trashes a, z, n | { @@ -1112,7 +1178,6 @@ Routines are read-only. | } | | routine main - | inputs foo | outputs vec | trashes a, z, n | { @@ -1128,7 +1193,7 @@ Indirect call. | ld x, 200 | } | - | routine main inputs bar outputs x, foo trashes a, z, n { + | routine main outputs x, foo trashes a, z, n { | copy bar, foo | call foo | } @@ -1142,7 +1207,7 @@ Calling the vector does indeed trash the things the vector says it does. | ld x, 200 | } | - | routine main inputs bar outputs x, foo trashes z, n { + | routine main outputs x, foo trashes z, n { | ld x, 0 | copy bar, foo | call foo @@ -1242,7 +1307,7 @@ Indirect goto. | ld x, 200 | } | - | routine main inputs bar outputs x trashes foo, a, z, n { + | routine main outputs x trashes foo, a, z, n { | copy bar, foo | goto foo | } @@ -1260,14 +1325,13 @@ vector says it does. | } | | routine sub - | inputs bar | trashes foo, a, x, z, n { | ld x, 0 | copy bar, foo | goto foo | } | - | routine main inputs bar + | routine main | outputs a | trashes foo, x, z, n { | call sub @@ -1286,7 +1350,6 @@ vector says it does. | } | | routine sub - | inputs bar | outputs x | trashes foo, a, z, n { | ld x, 0 @@ -1294,7 +1357,7 @@ vector says it does. | goto foo | } | - | routine main inputs bar + | routine main | outputs a | trashes foo, x, z, n { | call sub