From b20b664748671f8b62ce9d9fda45370047f8f447 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Thu, 8 Feb 2018 14:04:51 +0000 Subject: [PATCH] Implement the "union rule for trashes" when analyzing `if` blocks. --- HISTORY.md | 1 + README.md | 5 ----- src/sixtypical/analyzer.py | 28 ++++++++++++++++++++-------- tests/SixtyPical Analysis.md | 4 ++-- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 0f2e050..8d00f67 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,7 @@ History of SixtyPical * `copy` is now understood to trash `a`, thus `copy ..., a` is not valid. Indirect addressing is supported in `ld`, as in `ld a, [ptr] + y`, to compensate. +* Implements the "union rule for trashes" when analyzing `if` blocks. * Fixed bug where `trash` was not marking the location as being virtually altered. 0.11 diff --git a/README.md b/README.md index 756f013..ef9ea2c 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,6 @@ that call this routine. These might be forced to specify an initial value so that they can always be assumed to be meaningful. -### Union rule for trashes in `if` - -If one branch trashes {`a`} and the other branch trashes {`b`} then the whole -`if` statement trashes {`a`, `b`}. - ### Re-order routines and optimize tail-calls to fallthroughs Not because it saves 3 bytes, but because it's a neat trick. Doing it optimally diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 64083bc..c90c2e7 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -101,13 +101,6 @@ class Context(object): 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) - self._writeable = set(c._writeable) - def each_meaningful(self): for ref in self._meaningful: yield ref @@ -330,25 +323,44 @@ class Analyzer(object): context.set_touched(ref) context.set_unmeaningful(ref) elif opcode == 'if': + incoming_meaningful = set(context.each_meaningful()) + context1 = context.clone() context2 = context.clone() self.analyze_block(instr.block1, context1) if instr.block2 is not None: self.analyze_block(instr.block2, context2) + + outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful()) + outgoing_trashes = incoming_meaningful - outgoing_meaningful + # 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. for ref in context1.each_meaningful(): + if ref in outgoing_trashes: + continue context2.assert_meaningful( ref, exception_class=InconsistentInitializationError, message='initialized in block 1 but not in block 2 of `if {}`'.format(src) ) for ref in context2.each_meaningful(): + if ref in outgoing_trashes: + continue context1.assert_meaningful( ref, exception_class=InconsistentInitializationError, message='initialized in block 2 but not in block 1 of `if {}`'.format(src) ) - context.set_from(context1) + + # merge the contexts. this used to be a method called `set_from` + context._touched = set(context1._touched) | set(context2._touched) + context._meaningful = outgoing_meaningful + context._writeable = set(context1._writeable) | set(context2._writeable) + + for ref in outgoing_trashes: + context.set_touched(ref) + context.set_unmeaningful(ref) + elif opcode == 'repeat': # it will always be executed at least once, so analyze it having # been executed the first time. diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 1105302..39c8603 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1417,7 +1417,7 @@ trashes {`a`, `b`}. | trash x | } | } - ? UnmeaningfulOutputError: x in foo + ? ForbiddenWriteError: x in foo | routine foo | inputs a, x, z @@ -1429,7 +1429,7 @@ trashes {`a`, `b`}. | trash x | } | } - ? UnmeaningfulOutputError: a in foo + ? ForbiddenWriteError: a in foo ### repeat ###