From 0429e4bd90204e7df957edf28b94369335921b4d Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 20 Nov 2018 13:14:45 +0000 Subject: [PATCH 01/36] Make lexer greatly less inefficient on large source files. --- src/sixtypical/scanner.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sixtypical/scanner.py b/src/sixtypical/scanner.py index a85da67..40dfcfe 100644 --- a/src/sixtypical/scanner.py +++ b/src/sixtypical/scanner.py @@ -17,18 +17,20 @@ class Scanner(object): self.filename = filename self.token = None self.type = None + self.pos = 0 self.line_number = 1 self.scan() - def scan_pattern(self, pattern, type, token_group=1, rest_group=2): - pattern = r'^(' + pattern + r')(.*?)$' - match = re.match(pattern, self.text, re.DOTALL) + def scan_pattern(self, pattern, type, token_group=1): + pattern = r'(' + pattern + r')' + regexp = re.compile(pattern, flags=re.DOTALL) + match = regexp.match(self.text, pos=self.pos) if not match: return False else: self.type = type self.token = match.group(token_group) - self.text = match.group(rest_group) + self.pos += len(match.group(0)) self.line_number += self.token.count('\n') return True @@ -36,7 +38,7 @@ class Scanner(object): self.scan_pattern(r'[ \t\n\r]*', 'whitespace') while self.scan_pattern(r'\/\/.*?[\n\r]', 'comment'): self.scan_pattern(r'[ \t\n\r]*', 'whitespace') - if not self.text: + if self.pos >= len(self.text): self.token = None self.type = 'EOF' return @@ -44,20 +46,18 @@ class Scanner(object): return if self.scan_pattern(r'\d+', 'integer literal'): return - if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal', - token_group=2, rest_group=3): + if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal', token_group=2): # ecch self.token = str(eval('0x' + self.token)) return - if self.scan_pattern(r'\"(.*?)\"', 'string literal', - token_group=2, rest_group=3): + if self.scan_pattern(r'\"(.*?)\"', 'string literal', token_group=2): return if self.scan_pattern(r'\w+', 'identifier'): return if self.scan_pattern(r'.', 'unknown character'): return else: - raise AssertionError("this should never happen, self.text=({})".format(self.text)) + raise AssertionError("this should never happen, self.text=({}), self.pos=({})".format(self.text, self.pos)) def expect(self, token): if self.token == token: From e74c7f2b31613c489ea7c92fdde1a29c331857cb Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Fri, 23 Nov 2018 22:09:28 +0000 Subject: [PATCH 02/36] Initial attempt at 16-bit compare. Not super well tested yet. --- doc/SixtyPical.md | 20 +++++++++++--- src/sixtypical/analyzer.py | 6 +++++ src/sixtypical/compiler.py | 19 +++++++++++++ src/sixtypical/parser.py | 8 ++++++ tests/SixtyPical Analysis.md | 48 +++++++++++++++++++++++++++++++++ tests/SixtyPical Compilation.md | 20 ++++++++++++++ tests/SixtyPical Syntax.md | 10 +++++++ 7 files changed, 128 insertions(+), 3 deletions(-) diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index 1955fe8..be9d072 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -288,9 +288,9 @@ this mode is used. copy , -Reads from src and writes to dest. Differs from `st` in that is able to -copy more general types of data (for example, vectors,) and it trashes the -`z` and `n` flags and the `a` register. +Reads from src and writes to dest. Differs from `ld` and `st` in that +it is able to copy more general types of data (for example, vectors,) +and it trashes the `z` and `n` flags and the `a` register. * It is illegal if dest is read-only. * It is illegal if dest does not occur in the WRITES of the current routine. @@ -401,6 +401,20 @@ does not store the result anywhere, only sets the resulting flags. Affects n, z, and c flags, requiring that they be in the WRITES, and initializing them afterwards. +### compare ### + + compare , + +Subtracts the contents of src from dest, discarding the result +and only setting the resulting flags. Differs from `cmp` in +that it is able to work on more general types of data (notably, +words) and it trashes the `a` register. + +* It is illegal if src OR dest is uninitialized. + +Affects n, z, and c flags, requiring that they be in the WRITES, +and initializing them afterwards. + ### and, or, xor ### and , diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 98959e1..4b32631 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -508,6 +508,12 @@ class Analyzer(object): else: self.assert_type(TYPE_BYTE, src, dest) context.set_written(FLAG_Z, FLAG_N, FLAG_C) + elif opcode == 'compare': + context.assert_meaningful(src, dest) + self.assert_type(TYPE_WORD, src, dest) + context.set_touched(REG_A) + context.set_unmeaningful(REG_A) + context.set_written(FLAG_Z, FLAG_N, FLAG_C) elif opcode == 'and': if isinstance(src, IndexedRef): context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 3061443..d3a9f31 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -325,6 +325,8 @@ class Compiler(object): raise UnsupportedOpcodeError(instr) elif opcode == 'cmp': self.compile_cmp(instr, instr.src, instr.dest) + elif opcode == 'compare': + self.compile_compare(instr, instr.src, instr.dest) elif opcode in ('and', 'or', 'xor',): cls = { 'and': AND, @@ -406,6 +408,23 @@ class Compiler(object): else: self.emitter.emit(cls(Absolute(self.get_label(src.name)))) + def compile_compare(self, instr, src, dest): + """`instr` is only for reporting purposes""" + if not isinstance(src, LocationRef) or not isinstance(dest, LocationRef): + raise UnsupportedOpcodeError(instr) + if src.type != TYPE_WORD or dest.type != TYPE_WORD: + raise UnsupportedOpcodeError(instr) + + src_label = self.get_label(src.name) + dest_label = self.get_label(dest.name) + self.emitter.emit(LDA(Absolute(src_label))) + self.emitter.emit(CMP(Absolute(dest_label))) + end_label = Label('end_label') + self.emitter.emit(BNE(Relative(end_label))) + self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) + self.emitter.emit(CMP(Absolute(Offset(dest_label, 1)))) + self.emitter.resolve_label(end_label) + def compile_inc(self, instr, dest): """`instr` is only for reporting purposes""" if dest == REG_X: diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 07f6f1c..4440142 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -463,6 +463,14 @@ class Parser(object): dest = self.indlocexpr() instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) return instr + elif self.scanner.token in ("compare",): + opcode = self.scanner.token + self.scanner.scan() + dest = self.locexpr() + self.scanner.expect(',') + src = self.indexed_locexpr() + instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) + return instr elif self.scanner.consume("with"): self.scanner.expect("interrupts") self.scanner.expect("off") diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index b393e07..3d689e3 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1118,6 +1118,54 @@ Some rudimentary tests for `cmp`. | } ? UnmeaningfulReadError: a +### compare ### + +Some rudimentary tests for `compare`. + + | word za + | word zb + | + | define main routine + | inputs za, zb + | trashes a, z, c, n + | { + | compare za, zb + | } + = ok + + | word za + | word zb + | + | define main routine + | inputs za, zb + | trashes a, z, n + | { + | compare za, zb + | } + ? ForbiddenWriteError: c + + | word za + | word zb + | + | define main routine + | inputs za, zb + | trashes z, c, n + | { + | compare za, zb + | } + ? ForbiddenWriteError: a + + | word za + | word zb + | + | define main routine + | inputs za + | trashes z, c, n + | { + | compare za, zb + | } + ? UnmeaningfulReadError: zb + ### and ### Some rudimentary tests for `and`. diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 87984df..40943e2 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -385,6 +385,26 @@ Some instructions on tables. (3/3) = $081B DEC $081F,X = $081E RTS +Compiling `compare`. + + | word za @ 60001 + | word zb : 3003 + | + | define main routine + | inputs za, zb + | trashes a, z, c, n + | { + | compare za, zb + | } + = $080D LDA $081C + = $0810 CMP $EA61 + = $0813 BNE $081B + = $0815 LDA $081D + = $0818 CMP $EA62 + = $081B RTS + = $081C .byte $BB + = $081D .byte $0B + Compiling `if`. | define main routine diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 3409e63..253f66f 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -586,6 +586,16 @@ Buffers and pointers. | } = ok +Comparison of words. + + | word za + | word zb + | + | define main routine inputs za, zb { + | compare za, zb + | } + = ok + Routines can be defined in a new style. | typedef routine From f860db055c3e6db1a52b0193aa51c71d030464e1 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Fri, 23 Nov 2018 22:16:21 +0000 Subject: [PATCH 03/36] Note what's on this development branch so far. --- HISTORY.md | 6 ++++++ README.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 63e1128..1376472 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,12 @@ History of SixtyPical ===================== +0.18 +---- + +* Fixed pathological memory use in the lexical scanner - should + be much less inefficient now when parsing large source files. + 0.17 ---- diff --git a/README.md b/README.md index 48c4471..fc947c1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ SixtyPical ========== -_Version 0.17. Work-in-progress, everything is subject to change._ +_Version 0.18. Work-in-progress, everything is subject to change._ **SixtyPical** is a low-level programming language with advanced static analysis. Many of its primitive instructions resemble From a765a50c1e7da2a0c7766732eaefb05efd12edc0 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Fri, 23 Nov 2018 22:23:41 +0000 Subject: [PATCH 04/36] Notes on `compare`. --- HISTORY.md | 2 ++ TODO.md | 11 ----------- doc/SixtyPical.md | 6 ++++++ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 1376472..3d6fd45 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,8 @@ History of SixtyPical 0.18 ---- +* Added `compare` instruction, which is like `cmp` but can + directly compare two `word` memory locations (trashing `a`.) * Fixed pathological memory use in the lexical scanner - should be much less inefficient now when parsing large source files. diff --git a/TODO.md b/TODO.md index 908f893..e69cb35 100644 --- a/TODO.md +++ b/TODO.md @@ -1,17 +1,6 @@ TODO for SixtyPical =================== -### 16-bit `cmp` - -This is because we don't actually want `low` and `high` address operators -that turn `word` type into `byte`. - -This is because this immediately makes things harder (that is, effectively -impossible) to analyze. - -16-bit `cmp` also benefits from some special differences between `cmp` -and `sub` on 6502, so it would be nice to capture them. - ### Save values to other-than-the-stack Allow diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index be9d072..ef63f87 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -415,6 +415,12 @@ words) and it trashes the `a` register. Affects n, z, and c flags, requiring that they be in the WRITES, and initializing them afterwards. +Note that, like `cmp`, `compare` is not suitable for making a +signed comparison; this article, which mentions +techniques that a SixtyPical compiler could use to +implement `compare`, also explains why that is: +[Beyond 8-bit Unsigned Comparisons, by Bruce Clark](http://www.6502.org/tutorials/compare_beyond.html). + ### and, or, xor ### and , From dcc944d732e4be70be6318c16a0431a67276881f Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Fri, 23 Nov 2018 23:00:41 +0000 Subject: [PATCH 05/36] If add and sub can work on words, then cmp can work on words too. --- HISTORY.md | 4 ++-- doc/SixtyPical.md | 22 +++++++--------------- src/sixtypical/analyzer.py | 12 +++++------- src/sixtypical/compiler.py | 30 +++++++++++------------------- src/sixtypical/parser.py | 8 -------- tests/SixtyPical Analysis.md | 12 +++++------- tests/SixtyPical Compilation.md | 4 ++-- tests/SixtyPical Syntax.md | 10 ---------- 8 files changed, 32 insertions(+), 70 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 3d6fd45..32f6915 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,8 +4,8 @@ History of SixtyPical 0.18 ---- -* Added `compare` instruction, which is like `cmp` but can - directly compare two `word` memory locations (trashing `a`.) +* `cmp` instruction can now perform a 16-bit unsigned comparison + of `word` memory locations (at the cost of trashing `a`.) * Fixed pathological memory use in the lexical scanner - should be much less inefficient now when parsing large source files. diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index ef63f87..151fca3 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -376,6 +376,9 @@ and initializing them afterwards. dest and src continue to be initialized afterwards. +In addition, if dest is of `word` type, then src must also be of `word` +type, and in this case this instruction trashes the `a` register. + ### dec ### dec @@ -401,24 +404,13 @@ does not store the result anywhere, only sets the resulting flags. Affects n, z, and c flags, requiring that they be in the WRITES, and initializing them afterwards. -### compare ### +In addition, if dest is of `word` type, then src must also be of `word` +type, and in this case this instruction trashes the `a` register. - compare , - -Subtracts the contents of src from dest, discarding the result -and only setting the resulting flags. Differs from `cmp` in -that it is able to work on more general types of data (notably, -words) and it trashes the `a` register. - -* It is illegal if src OR dest is uninitialized. - -Affects n, z, and c flags, requiring that they be in the WRITES, -and initializing them afterwards. - -Note that, like `cmp`, `compare` is not suitable for making a +Note that, like `cmp` is not suitable for making a signed comparison; this article, which mentions techniques that a SixtyPical compiler could use to -implement `compare`, also explains why that is: +implement `cmp`, also explains why that is: [Beyond 8-bit Unsigned Comparisons, by Bruce Clark](http://www.6502.org/tutorials/compare_beyond.html). ### and, or, xor ### diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 4b32631..97f6e50 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -505,14 +505,12 @@ class Analyzer(object): context.assert_meaningful(src, dest) if isinstance(src, IndexedRef): context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) - else: + elif src.type == TYPE_BYTE: self.assert_type(TYPE_BYTE, src, dest) - context.set_written(FLAG_Z, FLAG_N, FLAG_C) - elif opcode == 'compare': - context.assert_meaningful(src, dest) - self.assert_type(TYPE_WORD, src, dest) - context.set_touched(REG_A) - context.set_unmeaningful(REG_A) + else: + self.assert_type(TYPE_WORD, src, dest) + context.set_touched(REG_A) + context.set_unmeaningful(REG_A) context.set_written(FLAG_Z, FLAG_N, FLAG_C) elif opcode == 'and': if isinstance(src, IndexedRef): diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index d3a9f31..8d6ed5d 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -325,8 +325,6 @@ class Compiler(object): raise UnsupportedOpcodeError(instr) elif opcode == 'cmp': self.compile_cmp(instr, instr.src, instr.dest) - elif opcode == 'compare': - self.compile_compare(instr, instr.src, instr.dest) elif opcode in ('and', 'or', 'xor',): cls = { 'and': AND, @@ -393,6 +391,17 @@ class Compiler(object): def compile_cmp(self, instr, src, dest): """`instr` is only for reporting purposes""" + if isinstance(src, LocationRef) and src.type == TYPE_WORD: + src_label = self.get_label(src.name) + dest_label = self.get_label(dest.name) + self.emitter.emit(LDA(Absolute(src_label))) + self.emitter.emit(CMP(Absolute(dest_label))) + end_label = Label('end_label') + self.emitter.emit(BNE(Relative(end_label))) + self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) + self.emitter.emit(CMP(Absolute(Offset(dest_label, 1)))) + self.emitter.resolve_label(end_label) + return cls = { 'a': CMP, 'x': CPX, @@ -408,23 +417,6 @@ class Compiler(object): else: self.emitter.emit(cls(Absolute(self.get_label(src.name)))) - def compile_compare(self, instr, src, dest): - """`instr` is only for reporting purposes""" - if not isinstance(src, LocationRef) or not isinstance(dest, LocationRef): - raise UnsupportedOpcodeError(instr) - if src.type != TYPE_WORD or dest.type != TYPE_WORD: - raise UnsupportedOpcodeError(instr) - - src_label = self.get_label(src.name) - dest_label = self.get_label(dest.name) - self.emitter.emit(LDA(Absolute(src_label))) - self.emitter.emit(CMP(Absolute(dest_label))) - end_label = Label('end_label') - self.emitter.emit(BNE(Relative(end_label))) - self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) - self.emitter.emit(CMP(Absolute(Offset(dest_label, 1)))) - self.emitter.resolve_label(end_label) - def compile_inc(self, instr, dest): """`instr` is only for reporting purposes""" if dest == REG_X: diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 4440142..07f6f1c 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -463,14 +463,6 @@ class Parser(object): dest = self.indlocexpr() instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) return instr - elif self.scanner.token in ("compare",): - opcode = self.scanner.token - self.scanner.scan() - dest = self.locexpr() - self.scanner.expect(',') - src = self.indexed_locexpr() - instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) - return instr elif self.scanner.consume("with"): self.scanner.expect("interrupts") self.scanner.expect("off") diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 3d689e3..e6dab0a 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1118,9 +1118,7 @@ Some rudimentary tests for `cmp`. | } ? UnmeaningfulReadError: a -### compare ### - -Some rudimentary tests for `compare`. +`cmp` can work on words. In this case, it trashes `a`. | word za | word zb @@ -1129,7 +1127,7 @@ Some rudimentary tests for `compare`. | inputs za, zb | trashes a, z, c, n | { - | compare za, zb + | cmp za, zb | } = ok @@ -1140,7 +1138,7 @@ Some rudimentary tests for `compare`. | inputs za, zb | trashes a, z, n | { - | compare za, zb + | cmp za, zb | } ? ForbiddenWriteError: c @@ -1151,7 +1149,7 @@ Some rudimentary tests for `compare`. | inputs za, zb | trashes z, c, n | { - | compare za, zb + | cmp za, zb | } ? ForbiddenWriteError: a @@ -1162,7 +1160,7 @@ Some rudimentary tests for `compare`. | inputs za | trashes z, c, n | { - | compare za, zb + | cmp za, zb | } ? UnmeaningfulReadError: zb diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 40943e2..d7e057c 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -385,7 +385,7 @@ Some instructions on tables. (3/3) = $081B DEC $081F,X = $081E RTS -Compiling `compare`. +Compiling 16-bit `cmp`. | word za @ 60001 | word zb : 3003 @@ -394,7 +394,7 @@ Compiling `compare`. | inputs za, zb | trashes a, z, c, n | { - | compare za, zb + | cmp za, zb | } = $080D LDA $081C = $0810 CMP $EA61 diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 253f66f..3409e63 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -586,16 +586,6 @@ Buffers and pointers. | } = ok -Comparison of words. - - | word za - | word zb - | - | define main routine inputs za, zb { - | compare za, zb - | } - = ok - Routines can be defined in a new style. | typedef routine From 2d767e9fe77d396cc5222be57ae0272ada31fbae Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Fri, 23 Nov 2018 23:11:06 +0000 Subject: [PATCH 06/36] Add example program that suggests we've got the arguments backwards. --- eg/rudiments/cmp.60p | 74 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 eg/rudiments/cmp.60p diff --git a/eg/rudiments/cmp.60p b/eg/rudiments/cmp.60p new file mode 100644 index 0000000..23ec7ee --- /dev/null +++ b/eg/rudiments/cmp.60p @@ -0,0 +1,74 @@ +// Should print ENLGG + +word w1 +word w2 + +define chrout routine + inputs a + trashes a + @ 65490 + +define main routine + outputs w1, w2 + trashes a, x, y, z, n, c, v +{ + copy 4000, w1 + copy 4000, w2 + + cmp w1, w2 + if z { + ld a, 69 // E + call chrout + } else { + ld a, 78 // N + call chrout + } + + copy 4000, w1 + copy 4001, w2 + + cmp w1, w2 + if z { + ld a, 69 // E + call chrout + } else { + ld a, 78 // N + call chrout + } + + copy 20000, w1 + copy 20001, w2 + + cmp w1, w2 + if c { + ld a, 71 // G + call chrout + } else { + ld a, 76 // L + call chrout + } + + copy 20001, w1 + copy 20001, w2 + + cmp w1, w2 + if c { + ld a, 71 // G + call chrout + } else { + ld a, 76 // L + call chrout + } + + copy 20002, w1 + copy 20001, w2 + + cmp w1, w2 + if c { + ld a, 71 // G + call chrout + } else { + ld a, 76 // L + call chrout + } +} From 046e43cd668e86057443f03ec1c87f91f20a5435 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 13:05:19 +0000 Subject: [PATCH 07/36] Add another example. Even more convincing that the order is wrong. --- eg/rudiments/cmp-byte.60p | 74 ++++++++++++++++++++++++++ eg/rudiments/{cmp.60p => cmp-word.60p} | 14 +++-- 2 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 eg/rudiments/cmp-byte.60p rename eg/rudiments/{cmp.60p => cmp-word.60p} (87%) diff --git a/eg/rudiments/cmp-byte.60p b/eg/rudiments/cmp-byte.60p new file mode 100644 index 0000000..c2613d9 --- /dev/null +++ b/eg/rudiments/cmp-byte.60p @@ -0,0 +1,74 @@ +// Should print ENGGL + +byte b + +define chrout routine + inputs a + trashes a + @ 65490 + +define main routine + outputs b + trashes a, x, y, z, n, c, v +{ + ld a, 40 + st a, b + + cmp a, b + if z { + ld a, 69 // E + call chrout + } else { + ld a, 78 // N + call chrout + } + + ld a, 41 + st a, b + ld a, 40 + + cmp a, b + if z { + ld a, 69 // E + call chrout + } else { + ld a, 78 // N + call chrout + } + + ld a, 20 + st a, b + + ld a, 21 + + cmp a, b // 21 >= 20 + if c { + ld a, 71 // G + call chrout + } else { + ld a, 76 // L + call chrout + } + + ld a, 20 + + cmp a, b // 20 >= 21 + if c { + ld a, 71 // G + call chrout + } else { + ld a, 76 // L + call chrout + } + + ld a, 19 + + cmp a, b // 19 < 21 + if c { + ld a, 71 // G + call chrout + } else { + ld a, 76 // L + call chrout + } +} diff --git a/eg/rudiments/cmp.60p b/eg/rudiments/cmp-word.60p similarity index 87% rename from eg/rudiments/cmp.60p rename to eg/rudiments/cmp-word.60p index 23ec7ee..ffbd46a 100644 --- a/eg/rudiments/cmp.60p +++ b/eg/rudiments/cmp-word.60p @@ -1,4 +1,4 @@ -// Should print ENLGG +// Should print ENGGL word w1 word w2 @@ -36,10 +36,10 @@ define main routine call chrout } - copy 20000, w1 + copy 20002, w1 copy 20001, w2 - cmp w1, w2 + cmp w1, w2 // 20002 >= 20001 if c { ld a, 71 // G call chrout @@ -49,9 +49,8 @@ define main routine } copy 20001, w1 - copy 20001, w2 - cmp w1, w2 + cmp w1, w2 // 20001 >= 20001 if c { ld a, 71 // G call chrout @@ -60,10 +59,9 @@ define main routine call chrout } - copy 20002, w1 - copy 20001, w2 + copy 20000, w1 - cmp w1, w2 + cmp w1, w2 // 20000 < 20001 if c { ld a, 71 // G call chrout From 94ee042a1e93457d1e9152a85ab0b78e8241e8df Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 13:12:55 +0000 Subject: [PATCH 08/36] Fix order of operands in word-sized `cmp`. --- doc/SixtyPical.md | 5 ++++- eg/rudiments/cmp-byte.60p | 4 ++-- src/sixtypical/compiler.py | 8 ++++---- tests/SixtyPical Compilation.md | 8 ++++---- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index 151fca3..a4b4d97 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -398,6 +398,9 @@ and initializing them afterwards. Subtracts the contents of src from dest (without considering carry) but does not store the result anywhere, only sets the resulting flags. +This means that `z` is set if src and dest are equal, +and `c` is set if dest is greater than or equal to src +(`c` is unset if dest is less than src.) * It is illegal if src OR dest is uninitialized. @@ -407,7 +410,7 @@ and initializing them afterwards. In addition, if dest is of `word` type, then src must also be of `word` type, and in this case this instruction trashes the `a` register. -Note that, like `cmp` is not suitable for making a +Note that `cmp` is not suitable for making a signed comparison; this article, which mentions techniques that a SixtyPical compiler could use to implement `cmp`, also explains why that is: diff --git a/eg/rudiments/cmp-byte.60p b/eg/rudiments/cmp-byte.60p index c2613d9..fdccd48 100644 --- a/eg/rudiments/cmp-byte.60p +++ b/eg/rudiments/cmp-byte.60p @@ -52,7 +52,7 @@ define main routine ld a, 20 - cmp a, b // 20 >= 21 + cmp a, b // 20 >= 20 if c { ld a, 71 // G call chrout @@ -63,7 +63,7 @@ define main routine ld a, 19 - cmp a, b // 19 < 21 + cmp a, b // 19 < 20 if c { ld a, 71 // G call chrout diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 8d6ed5d..a2b983d 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -394,12 +394,12 @@ class Compiler(object): if isinstance(src, LocationRef) and src.type == TYPE_WORD: src_label = self.get_label(src.name) dest_label = self.get_label(dest.name) - self.emitter.emit(LDA(Absolute(src_label))) - self.emitter.emit(CMP(Absolute(dest_label))) + self.emitter.emit(LDA(Absolute(dest_label))) + self.emitter.emit(CMP(Absolute(src_label))) end_label = Label('end_label') self.emitter.emit(BNE(Relative(end_label))) - self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) - self.emitter.emit(CMP(Absolute(Offset(dest_label, 1)))) + self.emitter.emit(LDA(Absolute(Offset(dest_label, 1)))) + self.emitter.emit(CMP(Absolute(Offset(src_label, 1)))) self.emitter.resolve_label(end_label) return cls = { diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index d7e057c..d61ef30 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -396,11 +396,11 @@ Compiling 16-bit `cmp`. | { | cmp za, zb | } - = $080D LDA $081C - = $0810 CMP $EA61 + = $080D LDA $EA61 + = $0810 CMP $081C = $0813 BNE $081B - = $0815 LDA $081D - = $0818 CMP $EA62 + = $0815 LDA $EA62 + = $0818 CMP $081D = $081B RTS = $081C .byte $BB = $081D .byte $0B From 9e9284bee5a986082ad27df0b68043952574e1a6 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 13:43:36 +0000 Subject: [PATCH 09/36] `goto` may only occur at the end of a block. --- src/sixtypical/parser.py | 2 ++ tests/SixtyPical Analysis.md | 6 +++++- tests/SixtyPical Syntax.md | 12 ++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 07f6f1c..9bae211 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -380,6 +380,8 @@ class Parser(object): self.scanner.expect('{') while not self.scanner.on('}'): instrs.append(self.instr()) + if isinstance(instrs[-1], SingleOp) and instrs[-1].opcode == 'goto': + break self.scanner.expect('}') return Block(self.scanner.line_number, instrs=instrs) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index b393e07..d190cc8 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -2990,7 +2990,11 @@ Calling the vector does indeed trash the things the vector says it does. | } | | define main routine trashes x, z, n { - | goto bar + | ld x, 0 + | if z { + | ld x, 1 + | goto bar + | } | ld x, 0 | } ? IllegalJumpError diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 3409e63..dc59a0f 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -571,6 +571,18 @@ goto. | } ? SyntaxError +`goto` may only be the final instruction in a block. + + | define bar routine trashes x, z, n { + | ld x, 200 + | } + | + | define main routine trashes x, z, n { + | goto bar + | ld x, 0 + | } + ? Expected '}', but found 'ld' + Buffers and pointers. | buffer[2048] buf From 9364a5fbec7ae918fb3822986b6c7d824c4a8f9b Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 13:56:32 +0000 Subject: [PATCH 10/36] Small step towards the goal of this branch. --- HISTORY.md | 1 + TODO.md | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 1376472..c2e70b6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,7 @@ History of SixtyPical 0.18 ---- +* Syntactically, `goto` may only appear at the end of a block. * Fixed pathological memory use in the lexical scanner - should be much less inefficient now when parsing large source files. diff --git a/TODO.md b/TODO.md index 908f893..c13654c 100644 --- a/TODO.md +++ b/TODO.md @@ -66,9 +66,6 @@ error. ### Tail-call optimization -More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot -appear elsewhere.) - If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, if the block is in tail position. The constraints should iron out the same both ways. From 6dbce205d15e34e9297560019ee2de9eb6ea7bab Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 14:25:43 +0000 Subject: [PATCH 11/36] `goto` is no longer restricted to appearing in tail position. --- HISTORY.md | 2 ++ TODO.md | 1 - src/sixtypical/analyzer.py | 6 ++-- tests/SixtyPical Analysis.md | 59 ++++++++++++++++++++++++++++-------- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index c2e70b6..0bb7f73 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,6 +5,8 @@ History of SixtyPical ---- * Syntactically, `goto` may only appear at the end of a block. + It need no longer be the final instruction in a routine, + as long as the type context is consistent at every exit. * Fixed pathological memory use in the lexical scanner - should be much less inefficient now when parsing large source files. diff --git a/TODO.md b/TODO.md index c13654c..b482ecb 100644 --- a/TODO.md +++ b/TODO.md @@ -69,7 +69,6 @@ error. If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, if the block is in tail position. The constraints should iron out the same both ways. -And - once we have this - why do we need `goto` to be in tail position, strictly? As long as the routine has consistent type context every place it exits, that should be fine. ### "Include" directives diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 98959e1..f089832 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -433,9 +433,6 @@ class Analyzer(object): dest = instr.dest src = instr.src - if context.encountered_gotos(): - raise IllegalJumpError(instr, instr) - if opcode == 'ld': if isinstance(src, IndexedRef): context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) @@ -735,6 +732,9 @@ class Analyzer(object): if instr.src is not None: context.assert_meaningful(instr.src) + if context.encountered_gotos(): + raise IllegalJumpError(instr, instr) + def analyze_for(self, instr, context): context.assert_meaningful(instr.dest) context.assert_writeable(instr.dest) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index d190cc8..8d60571 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -2280,7 +2280,7 @@ But only if they are bytes. | } ? TypeMismatchError -A `goto` cannot appear within a `save` block, even if it is otherwise in tail position. +A `goto` cannot appear within a `save` block. | define other routine | trashes a, z, n @@ -2325,8 +2325,7 @@ A `goto` cannot appear within a `save` block, even if it is otherwise in tail po | } = ok -A `goto` cannot appear within a `with interrupts` block, even if it is -otherwise in tail position. +A `goto` cannot appear within a `with interrupts` block. | vector routine | inputs x @@ -2973,7 +2972,26 @@ Calling the vector does indeed trash the things the vector says it does. | } ? UnmeaningfulOutputError: x -`goto`, if present, must be in tail position (the final instruction in a routine.) +For now at least, you cannot have a `goto` inside a 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. It is only important that the type context at every +`goto` is compatible with the type context at the end of +the routine. | define bar routine trashes x, z, n { | ld x, 200 @@ -2995,9 +3013,8 @@ Calling the vector does indeed trash the things the vector says it does. | ld x, 1 | goto bar | } - | ld x, 0 | } - ? IllegalJumpError + = ok | define bar routine trashes x, z, n { | ld x, 200 @@ -3009,6 +3026,7 @@ Calling the vector does indeed trash the things the vector says it does. | ld x, 1 | goto bar | } + | goto bar | } = ok @@ -3024,7 +3042,7 @@ Calling the vector does indeed trash the things the vector says it does. | } | ld x, 0 | } - ? IllegalJumpError + = ok | define bar routine trashes x, z, n { | ld x, 200 @@ -3057,7 +3075,21 @@ Calling the vector does indeed trash the things the vector says it does. | } = ok -For the purposes of `goto`, the end of a loop is never tail position. + | 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 @@ -3065,12 +3097,15 @@ For the purposes of `goto`, the end of a loop is never tail position. | | define main routine trashes x, z, n { | ld x, 0 - | repeat { - | inc x + | if z { + | ld x, 1 | goto bar - | } until z + | } else { + | ld x, 0 + | } + | goto bar | } - ? IllegalJumpError + = ok Can't `goto` a routine that outputs or trashes more than the current routine. From 0c481845e9a7caf7ff0c4c80782df13b2e3476a2 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 14:52:59 +0000 Subject: [PATCH 12/36] Add failing test. --- tests/SixtyPical Analysis.md | 251 ++++++++++++++++++++++------------- 1 file changed, 161 insertions(+), 90 deletions(-) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 8d60571..b128264 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -2972,7 +2972,7 @@ Calling the vector does indeed trash the things the vector says it does. | } ? UnmeaningfulOutputError: x -For now at least, you cannot have a `goto` inside a loop. +For now at least, you cannot have a `goto` inside a `repeat` loop. | define bar routine trashes x, z, n { | ld x, 200 @@ -2989,78 +2989,130 @@ For now at least, you cannot have a `goto` inside a loop. `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. It is only important that the type context at every +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 + +It is, however, important that the type context at every `goto` is compatible with the type context at the end of the 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 { + | define bar routine + | inputs x + | trashes x, z, n + | { | ld x, 200 | } | @@ -3072,40 +3124,59 @@ the routine. | } else { | ld x, 0 | } + | ld x, 1 | } = ok - | define bar routine trashes x, z, n { +Here, we try to trash x before gotoing a routine that inputs x. + + | define bar routine + | inputs x + | trashes x, z, n + | { | ld x, 200 | } | - | define main routine trashes x, z, n { + | 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, 0 + | ld x, 2 | } - | ld x, 0 + | ld a, 1 | } - = ok + ? UnmeaningfulReadError - | 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 +TODO: we should have a lot more test cases for the above, here. Can't `goto` a routine that outputs or trashes more than the current routine. From 27aca3dd86ab6fcbeca118a35b71495252741431 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 15:10:18 +0000 Subject: [PATCH 13/36] Looks like, when we "goto", we should "pull in" the constraints. --- src/sixtypical/analyzer.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index f089832..239c3bf 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -390,17 +390,22 @@ class Analyzer(object): print('-' * 79) print('') - # even if we goto another routine, we can't trash an output. + # these all apply whether we encountered goto(s) in this routine, or not...: + + # can't trash an output. for ref in trashed: if ref in type_.outputs: raise UnmeaningfulOutputError(routine, ref.name) - if not context.encountered_gotos(): - for ref in type_.outputs: - context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError) - for ref in context.each_touched(): - if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref): - raise ForbiddenWriteError(routine, ref.name) + # all outputs are meaningful. + for ref in type_.outputs: + context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError) + + # if something was touched, then it should have been declared to be writable. + for ref in context.each_touched(): + if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref): + raise ForbiddenWriteError(routine, ref.name) + self.current_routine = None return context From 0ca545c89a0bf5744914d509433ffd5e619892a8 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 15:44:39 +0000 Subject: [PATCH 14/36] Begin working out what happens when you encounter a goto. --- src/sixtypical/analyzer.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 239c3bf..7fadd42 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -676,6 +676,28 @@ class Analyzer(object): self.assert_affected_within('trashes', type_, current_type) context.encounter_gotos(set([instr.location])) + + # now that we have encountered a goto here, we set the + # context here to match what someone calling the goto'ed + # function directly, would expect. (which makes sense + # when you think about it; if this goto's F, then calling + # this is like calling F, from the perspective of what is + # returned. + + for ref in type_.outputs: + context.set_touched(ref) # ? + context.set_written(ref) + + for ref in type_.trashes: + context.assert_writeable(ref) + context.set_touched(ref) + context.set_unmeaningful(ref) + + # TODO is that... all we have to do? You'll note the above + # is a lot like call. We do rely on, if we are in a branch, + # the branch-merge to take care of... a lot? The fact that + # we don't actually continue on from here, I mean. + elif opcode == 'trash': context.set_touched(instr.dest) context.set_unmeaningful(instr.dest) From 21e623f3ad6d7170cb1c483a9e9e70a609b1beb9 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 16:59:16 +0000 Subject: [PATCH 15/36] Collect exit contexts. Disturbs fallthru analysis, but otherwise? --- src/sixtypical/analyzer.py | 98 ++++++++++++++++++++++++++++-------- tests/SixtyPical Analysis.md | 28 ++++++++++- 2 files changed, 104 insertions(+), 22 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 7fadd42..fd7e28d 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -101,6 +101,7 @@ class Context(object): self._touched = set() self._range = dict() self._writeable = set() + self._terminated = False self._gotos_encountered = set() for ref in inputs: @@ -132,6 +133,13 @@ class Context(object): c._writeable = set(self._writeable) return c + def update_from(self, other): + self.routines = other.routines + self.routine = other.routine + self._touched = set(other._touched) + self._range = dict(other._range) + self._writeable = set(other._writeable) + def each_meaningful(self): for ref in self._range.keys(): yield ref @@ -279,6 +287,13 @@ class Context(object): 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 assert_types_for_read_table(self, instr, src, dest, type_): if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_): raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name)) @@ -364,19 +379,20 @@ class Analyzer(object): def analyze_routine(self, routine): assert isinstance(routine, Routine) - self.current_routine = routine if routine.block is None: # it's an extern, that's fine return + + self.current_routine = routine type_ = routine.location.type context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes) + self.exit_contexts = [] if self.debug: print("at start of routine `{}`:".format(routine.name)) print(context) self.analyze_block(routine.block, context) - trashed = set(context.each_touched()) - set(context.each_meaningful()) if self.debug: print("at end of routine `{}`:".format(routine.name)) @@ -390,6 +406,20 @@ class Analyzer(object): print('-' * 79) print('') + if self.exit_contexts: + # check that they are all consistent + exit_context = self.exit_contexts[0] + exit_meaningful = set(exit_context.each_meaningful()) + exit_touched = set(exit_context.each_touched()) + for ex in self.exit_contexts[1:]: + if set(ex.each_meaningful()) != exit_meaningful: + raise InconsistentInitializationError('?') + if set(ex.each_touched()) != exit_touched: + raise InconsistentInitializationError('?') + context.update_from(exit_context) + + trashed = set(context.each_touched()) - set(context.each_meaningful()) + # these all apply whether we encountered goto(s) in this routine, or not...: # can't trash an output. @@ -406,6 +436,7 @@ class Analyzer(object): if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref): raise ForbiddenWriteError(routine, ref.name) + self.exit_contexts = None self.current_routine = None return context @@ -438,6 +469,9 @@ class Analyzer(object): dest = instr.dest src = instr.src + if context.has_terminated(): + raise IllegalJumpError(instr, instr) # TODO: maybe a better name for this + if opcode == 'ld': if isinstance(src, IndexedRef): context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) @@ -677,26 +711,38 @@ class Analyzer(object): context.encounter_gotos(set([instr.location])) - # now that we have encountered a goto here, we set the + # Now that we have encountered a goto, we update the # context here to match what someone calling the goto'ed # function directly, would expect. (which makes sense # when you think about it; if this goto's F, then calling # this is like calling F, from the perspective of what is - # returned. + # returned.) + # + # However, this isn't the current context anymore. This + # is an exit context of this routine. + + exit_context = context.clone() for ref in type_.outputs: - context.set_touched(ref) # ? - context.set_written(ref) + exit_context.set_touched(ref) # ? + exit_context.set_written(ref) for ref in type_.trashes: - context.assert_writeable(ref) - context.set_touched(ref) - context.set_unmeaningful(ref) + exit_context.assert_writeable(ref) + exit_context.set_touched(ref) + exit_context.set_unmeaningful(ref) - # TODO is that... all we have to do? You'll note the above - # is a lot like call. We do rely on, if we are in a branch, - # the branch-merge to take care of... a lot? The fact that - # we don't actually continue on from here, I mean. + self.exit_contexts.append(exit_context) + + # When we get to the end, we'll check that all the + # exit contexts are consistent with each other. + + # We set the current context as having terminated. + # If we are in a branch, the merge will deal with + # having terminated. If we are at the end of the + # routine, the routine end will deal with that. + + context.set_terminated() elif opcode == 'trash': context.set_touched(instr.dest) @@ -736,11 +782,21 @@ class Analyzer(object): message='initialized in block 2 but not in block 1 of `if {}`'.format(instr.src) ) - # merge the contexts. this used to be a method called `set_from` - context._touched = set(context1._touched) | set(context2._touched) - context.set_meaningful(*list(outgoing_meaningful)) - context._writeable = set(context1._writeable) | set(context2._writeable) - context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos()) + # merge the contexts. + + # first, the easy case: if one of the contexts has terminated, just use the other one. + # if both have terminated, we return a terminated context, and that's OK. + + if context1.has_terminated(): + context.update_from(context2) + elif context2.has_terminated(): + context.update_from(context1) + else: + # the more complicated case: merge the contents of the contexts. + context._touched = set(context1._touched) | set(context2._touched) + context.set_meaningful(*list(outgoing_meaningful)) + context._writeable = set(context1._writeable) | set(context2._writeable) + context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos()) for ref in outgoing_trashes: context.set_touched(ref) @@ -753,15 +809,15 @@ class Analyzer(object): if instr.src is not None: # None indicates 'repeat forever' context.assert_meaningful(instr.src) + if context.encountered_gotos(): + raise IllegalJumpError(instr, instr) + # now analyze it having been executed a second time, with the context # of it having already been executed. self.analyze_block(instr.block, context) if instr.src is not None: context.assert_meaningful(instr.src) - if context.encountered_gotos(): - raise IllegalJumpError(instr, instr) - def analyze_for(self, instr, context): context.assert_meaningful(instr.dest) context.assert_writeable(instr.dest) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index b128264..6b8ac1e 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -3174,7 +3174,33 @@ Here, we declare that main outputs a, but we goto a routine that does not output | } | ld a, 1 | } - ? UnmeaningfulReadError + ? 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 TODO: we should have a lot more test cases for the above, here. From a53a3529ea09d187eccc6ac36714e18d77fe09f2 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 17:06:07 +0000 Subject: [PATCH 16/36] Better name for this error. --- src/sixtypical/analyzer.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index fd7e28d..ad0c636 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -46,6 +46,12 @@ class IllegalJumpError(StaticAnalysisError): pass +class TerminatedContextError(StaticAnalysisError): + """What the program is doing here is not valid, due to preceding `goto`s, + which make this dead code.""" + pass + + class RangeExceededError(StaticAnalysisError): pass @@ -139,6 +145,8 @@ class Context(object): self._touched = set(other._touched) self._range = dict(other._range) self._writeable = set(other._writeable) + self._terminated = other._terminated + self._gotos_encounters = set(other._gotos_encountered) def each_meaningful(self): for ref in self._range.keys(): @@ -416,6 +424,7 @@ class Analyzer(object): raise InconsistentInitializationError('?') if set(ex.each_touched()) != exit_touched: raise InconsistentInitializationError('?') + # FIXME: confirm writeable sets are the same too? context.update_from(exit_context) trashed = set(context.each_touched()) - set(context.each_meaningful()) @@ -470,7 +479,7 @@ class Analyzer(object): src = instr.src if context.has_terminated(): - raise IllegalJumpError(instr, instr) # TODO: maybe a better name for this + raise TerminatedContextError(instr, instr) if opcode == 'ld': if isinstance(src, IndexedRef): From 07ec6752ee2bc8b51b1c3212ed351bb8c50962ff Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 17:09:38 +0000 Subject: [PATCH 17/36] Introduce yet another new error. --- src/sixtypical/analyzer.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index ad0c636..edd067c 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -34,6 +34,11 @@ class InconsistentInitializationError(StaticAnalysisError): pass +class InconsistentExitError(StaticAnalysisError): + """The type context differs at two different exit points of the routine.""" + pass + + class ForbiddenWriteError(StaticAnalysisError): pass @@ -156,6 +161,10 @@ class Context(object): 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: @@ -419,12 +428,14 @@ class Analyzer(object): exit_context = self.exit_contexts[0] exit_meaningful = set(exit_context.each_meaningful()) exit_touched = set(exit_context.each_touched()) + exit_writeable = set(exit_context.each_writeable()) for ex in self.exit_contexts[1:]: if set(ex.each_meaningful()) != exit_meaningful: - raise InconsistentInitializationError('?') + raise InconsistentExitError("Exit contexts are not consistent") if set(ex.each_touched()) != exit_touched: - raise InconsistentInitializationError('?') - # FIXME: confirm writeable sets are the same too? + raise InconsistentExitError("Exit contexts are not consistent") + if set(ex.each_writeable()) != exit_writeable: + raise InconsistentExitError("Exit contexts are not consistent") context.update_from(exit_context) trashed = set(context.each_touched()) - set(context.each_meaningful()) From d1befe7123ef3282f16972215332954b801e15bd Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 17:11:37 +0000 Subject: [PATCH 18/36] All tests pass again -- but this needs lots more tests please. --- src/sixtypical/analyzer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index edd067c..26dbbbb 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -816,7 +816,10 @@ class Analyzer(object): context._touched = set(context1._touched) | set(context2._touched) context.set_meaningful(*list(outgoing_meaningful)) context._writeable = set(context1._writeable) | set(context2._writeable) - context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos()) + + # in both cases, we need to merge the encountered gotos, in order that + # fallthru optimization continues to work correctly. + context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos()) for ref in outgoing_trashes: context.set_touched(ref) From 3814d2624dc134757aec9d21c1aa33cfa07115c2 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 17:26:58 +0000 Subject: [PATCH 19/36] Improve tests. --- tests/SixtyPical Analysis.md | 27 ++++++++++++++++++++++++--- tests/SixtyPical Syntax.md | 7 +++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 6b8ac1e..6ee6410 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -3105,7 +3105,28 @@ routine. | } = ok -It is, however, important that the type context at every +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. @@ -3128,7 +3149,7 @@ the routine. | } = ok -Here, we try to trash x before gotoing a routine that inputs x. +Here, we try to trash `x` before `goto`ing a routine that inputs `x`. | define bar routine | inputs x @@ -3152,7 +3173,7 @@ Here, we try to trash x before gotoing a routine that inputs x. | } ? UnmeaningfulReadError: x -Here, we declare that main outputs a, but we goto a routine that does not output a. +Here, we declare that main outputs `a`, but we `goto` a routine that does not output `a`. | define bar routine | inputs x diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index dc59a0f..7a660c8 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -551,6 +551,9 @@ goto. | } = ok +The label doesn't have to be defined yet at the point +in the program text where it is `goto`d. + | define main routine { | goto foo | } @@ -559,6 +562,8 @@ goto. | } = ok +Syntactically, you can `goto` a vector. + | vector routine foo | | define main routine { @@ -566,6 +571,8 @@ goto. | } = ok +But you can't `goto` a label that never gets defined. + | define main routine { | goto foo | } From d598b505e000ddf0dab2b5d78482b590d1efde97 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 27 Nov 2018 17:40:25 +0000 Subject: [PATCH 20/36] Add more tests. --- tests/SixtyPical Analysis.md | 97 ++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 6ee6410..eb995e5 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -3223,6 +3223,103 @@ Here, we declare that main outputs a, and we goto a routine that outputs a so th | } = 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 + TODO: we should have a lot more test cases for the above, here. Can't `goto` a routine that outputs or trashes more than the current routine. From 5d01820bb1ce1b598f59d518a9e9a57c414c2a95 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 11 Dec 2018 17:17:15 +0000 Subject: [PATCH 21/36] Small reorganization of example programs. --- eg/rudiments/README.md | 3 ++- eg/rudiments/errorful/README.md | 4 ++++ eg/rudiments/{add-fail.60p => errorful/add.60p} | 0 .../{range-error.60p => errorful/range.60p} | 0 .../{bad-vector.60p => errorful/vector.60p} | 0 eg/rudiments/vector-table.60p | 14 ++++++++++---- 6 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 eg/rudiments/errorful/README.md rename eg/rudiments/{add-fail.60p => errorful/add.60p} (100%) rename eg/rudiments/{range-error.60p => errorful/range.60p} (100%) rename eg/rudiments/{bad-vector.60p => errorful/vector.60p} (100%) diff --git a/eg/rudiments/README.md b/eg/rudiments/README.md index 51230f4..0567477 100644 --- a/eg/rudiments/README.md +++ b/eg/rudiments/README.md @@ -1,7 +1,8 @@ This directory contains example sources which demonstrate the rudiments of SixtyPical. -Some are meant to fail and produce an error message. +Examples that are meant to fail and produce an error message +are in the `errorful/` subdirectory. They are not meant to be specific to any architecture, but many do assume the existence of a routine at 65490 which diff --git a/eg/rudiments/errorful/README.md b/eg/rudiments/errorful/README.md new file mode 100644 index 0000000..e5b307a --- /dev/null +++ b/eg/rudiments/errorful/README.md @@ -0,0 +1,4 @@ +This directory contains example SixtyPical sources which +are intentionally invalid (for demonstration purposes) +and are expected to elicit an error message from a +SixtyPical implementation. diff --git a/eg/rudiments/add-fail.60p b/eg/rudiments/errorful/add.60p similarity index 100% rename from eg/rudiments/add-fail.60p rename to eg/rudiments/errorful/add.60p diff --git a/eg/rudiments/range-error.60p b/eg/rudiments/errorful/range.60p similarity index 100% rename from eg/rudiments/range-error.60p rename to eg/rudiments/errorful/range.60p diff --git a/eg/rudiments/bad-vector.60p b/eg/rudiments/errorful/vector.60p similarity index 100% rename from eg/rudiments/bad-vector.60p rename to eg/rudiments/errorful/vector.60p diff --git a/eg/rudiments/vector-table.60p b/eg/rudiments/vector-table.60p index 4362012..b6cce2a 100644 --- a/eg/rudiments/vector-table.60p +++ b/eg/rudiments/vector-table.60p @@ -3,6 +3,14 @@ // Prints "AABAB". // +// TODO: this doesn't pass the analyzer currently, which suggests a bug. +// +// RangeExceededError: Possible range of x:byte (0, 255) exceeds +// acceptable range of vectors:vector table[32] (0, 31) (in main, line 57) +// +// (despite various attempts to work around by calling a setup routine, etc.) +// It should really be able to figure out that the range of x is 0..4 there. + vector routine trashes a, z, n print @@ -49,10 +57,8 @@ define main routine copy printb, vectors + x ld x, 0 - repeat { + for x up to 4 { copy vectors + x, print call print - inc x - cmp x, 5 - } until z + } } From 3a4c2e46c10cddaad35f690e27a5f58c78f41902 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 11 Dec 2018 17:35:01 +0000 Subject: [PATCH 22/36] `inc` and `dec` on a known range usually keeps it known, now. --- HISTORY.md | 2 ++ eg/rudiments/vector-table.60p | 8 ------- src/sixtypical/analyzer.py | 15 +++++++++++- tests/SixtyPical Analysis.md | 43 +++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index c15b971..dda7e4c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,6 +7,8 @@ History of SixtyPical * Syntactically, `goto` may only appear at the end of a block. It need no longer be the final instruction in a routine, as long as the type context is consistent at every exit. +* When the range of a location is known, `inc` and `dec` + on it will usually shift the known instead of invalidating it. * `cmp` instruction can now perform a 16-bit unsigned comparison of `word` memory locations (at the cost of trashing `a`.) * Fixed pathological memory use in the lexical scanner - should diff --git a/eg/rudiments/vector-table.60p b/eg/rudiments/vector-table.60p index b6cce2a..6d6e98a 100644 --- a/eg/rudiments/vector-table.60p +++ b/eg/rudiments/vector-table.60p @@ -3,14 +3,6 @@ // Prints "AABAB". // -// TODO: this doesn't pass the analyzer currently, which suggests a bug. -// -// RangeExceededError: Possible range of x:byte (0, 255) exceeds -// acceptable range of vectors:vector table[32] (0, 31) (in main, line 57) -// -// (despite various attempts to work around by calling a setup routine, etc.) -// It should really be able to figure out that the range of x is 0..4 there. - vector routine trashes a, z, n print diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index cabff6d..9837424 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -597,7 +597,20 @@ class Analyzer(object): else: self.assert_type(TYPE_BYTE, dest) context.set_written(dest, FLAG_Z, FLAG_N) - context.invalidate_range(dest) + bottom = context.get_bottom_of_range(dest) + top = context.get_top_of_range(dest) + if opcode == 'inc': + if bottom == top and top < 255: + context.set_range(dest, bottom + 1, top + 1) + else: + context.invalidate_range(dest) + elif opcode == 'dec': + if bottom == top and bottom > 0: + context.set_range(dest, bottom - 1, top - 1) + else: + context.invalidate_range(dest) + else: + raise NotImplementedError elif opcode in ('shl', 'shr'): context.assert_meaningful(dest, FLAG_C) if isinstance(dest, IndexedRef): diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 071553b..0331e7e 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -762,6 +762,49 @@ no longer be guaranteed. | } ? 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. From d3f730cc76499403aa9eaa45284b308e8338dd15 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 11 Dec 2018 17:55:08 +0000 Subject: [PATCH 23/36] Move towards greater platform-agnosticism in these examples. --- eg/rudiments/README.md | 20 ++++++++++++++------ eg/rudiments/call.60p | 6 ++---- eg/rudiments/cmp-byte.60p | 6 +----- eg/rudiments/cmp-word.60p | 6 +----- eg/rudiments/conditional.60p | 7 +++---- eg/rudiments/conditional2.60p | 6 ++---- eg/rudiments/goto.60p | 6 ++---- eg/rudiments/loop.60p | 6 ++---- eg/rudiments/memloc.60p | 8 +++----- eg/rudiments/new-style-routine.60p | 22 ---------------------- eg/rudiments/print.60p | 6 ++---- eg/rudiments/support/c64.60p | 6 ++++++ eg/rudiments/support/vic20.60p | 6 ++++++ eg/rudiments/vector-inc.60p | 21 --------------------- eg/rudiments/vector-main.60p | 22 ---------------------- eg/rudiments/vector-table.60p | 10 ++-------- eg/rudiments/vector.60p | 8 +++----- 17 files changed, 49 insertions(+), 123 deletions(-) delete mode 100644 eg/rudiments/new-style-routine.60p create mode 100644 eg/rudiments/support/c64.60p create mode 100644 eg/rudiments/support/vic20.60p delete mode 100644 eg/rudiments/vector-inc.60p delete mode 100644 eg/rudiments/vector-main.60p diff --git a/eg/rudiments/README.md b/eg/rudiments/README.md index 0567477..032249f 100644 --- a/eg/rudiments/README.md +++ b/eg/rudiments/README.md @@ -4,10 +4,18 @@ the rudiments of SixtyPical. Examples that are meant to fail and produce an error message are in the `errorful/` subdirectory. -They are not meant to be specific to any architecture, but -many do assume the existence of a routine at 65490 which -outputs the value of the accumulator as an ASCII character, +These files are intended to be architecture-agnostic. +For the ones that do produce output, an appropriate source +under `platform/`, should be included first, like + + sixtypical platform/c64.60p vector-table.60p + +so that system entry points such as `chrout` are defined. + +`chrout` is a routine with outputs the value of the accumulator +as an ASCII character, disturbing none of the other registers, simply for the purposes of producing some observable output. -(This is an address of a KERNAL routine which does this -on both the Commodore 64 and the Commodore VIC-20, so these -sources should be usable on these architectures.) + +(There is a KERNAL routine which does this on both the +Commodore 64 and the Commodore VIC-20. It should not be hard +to find or write such a routine for most other architectures.) diff --git a/eg/rudiments/call.60p b/eg/rudiments/call.60p index a7fc616..47eb9ed 100644 --- a/eg/rudiments/call.60p +++ b/eg/rudiments/call.60p @@ -1,7 +1,5 @@ -define chrout routine - inputs a - trashes a - @ 65490 +// Include `support/${PLATFORM}.60p` before this source +// Should print ??? define print routine trashes a, z, n diff --git a/eg/rudiments/cmp-byte.60p b/eg/rudiments/cmp-byte.60p index fdccd48..21e1f7b 100644 --- a/eg/rudiments/cmp-byte.60p +++ b/eg/rudiments/cmp-byte.60p @@ -1,12 +1,8 @@ +// Include `support/${PLATFORM}.60p` before this source // Should print ENGGL byte b -define chrout routine - inputs a - trashes a - @ 65490 - define main routine outputs b trashes a, x, y, z, n, c, v diff --git a/eg/rudiments/cmp-word.60p b/eg/rudiments/cmp-word.60p index ffbd46a..853c08e 100644 --- a/eg/rudiments/cmp-word.60p +++ b/eg/rudiments/cmp-word.60p @@ -1,13 +1,9 @@ +// Include `support/${PLATFORM}.60p` before this source // Should print ENGGL word w1 word w2 -define chrout routine - inputs a - trashes a - @ 65490 - define main routine outputs w1, w2 trashes a, x, y, z, n, c, v diff --git a/eg/rudiments/conditional.60p b/eg/rudiments/conditional.60p index 3cdb1fb..bf7c637 100644 --- a/eg/rudiments/conditional.60p +++ b/eg/rudiments/conditional.60p @@ -1,7 +1,6 @@ -define chrout routine - inputs a - trashes a - @ 65490 +// Demonstrates vector tables. +// Include `support/${PLATFORM}.60p` before this source +// Should print ??? define main routine trashes a, x, y, z, n, c, v diff --git a/eg/rudiments/conditional2.60p b/eg/rudiments/conditional2.60p index 6483cfd..6c37e4e 100644 --- a/eg/rudiments/conditional2.60p +++ b/eg/rudiments/conditional2.60p @@ -1,7 +1,5 @@ -define chrout routine - inputs a - trashes a - @ 65490 +// Include `support/${PLATFORM}.60p` before this source +// Should print ??? define main routine trashes a, x, y, z, n, c, v diff --git a/eg/rudiments/goto.60p b/eg/rudiments/goto.60p index 6aafcbb..faa0c4e 100644 --- a/eg/rudiments/goto.60p +++ b/eg/rudiments/goto.60p @@ -1,7 +1,5 @@ -define chrout routine - inputs a - trashes a - @ 65490 +// Include `support/${PLATFORM}.60p` before this source +// Should print ??? define bar routine trashes a, z, n { ld a, 66 diff --git a/eg/rudiments/loop.60p b/eg/rudiments/loop.60p index 2f3d95b..40598b1 100644 --- a/eg/rudiments/loop.60p +++ b/eg/rudiments/loop.60p @@ -1,7 +1,5 @@ -define chrout routine - inputs a - trashes a - @ 65490 +// Include `support/${PLATFORM}.60p` before this source +// Should print ??? define main routine trashes a, y, z, n, c diff --git a/eg/rudiments/memloc.60p b/eg/rudiments/memloc.60p index d6e49d1..2a255d5 100644 --- a/eg/rudiments/memloc.60p +++ b/eg/rudiments/memloc.60p @@ -1,9 +1,7 @@ -byte foo +// Include `support/${PLATFORM}.60p` before this source +// Should print ??? -define chrout routine - inputs a - trashes a - @ 65490 +byte foo define print routine inputs foo diff --git a/eg/rudiments/new-style-routine.60p b/eg/rudiments/new-style-routine.60p deleted file mode 100644 index 1b58c9b..0000000 --- a/eg/rudiments/new-style-routine.60p +++ /dev/null @@ -1,22 +0,0 @@ -// SixtyPical 0.11 introduced a new syntax for defining routines -// (routine was made into a type, and you can now say: define name type.) - -typedef routine - inputs x - outputs x - trashes z, n - routine_type - -vector routine_type vec - -define foo routine_type -{ - inc x -} - -define main routine - outputs vec - trashes a, z, n -{ - copy foo, vec -} diff --git a/eg/rudiments/print.60p b/eg/rudiments/print.60p index d060ebc..7589930 100644 --- a/eg/rudiments/print.60p +++ b/eg/rudiments/print.60p @@ -1,7 +1,5 @@ -define chrout routine - inputs a - trashes a - @ 65490 +// Include `support/${PLATFORM}.60p` before this source +// Should print A define main routine inputs a diff --git a/eg/rudiments/support/c64.60p b/eg/rudiments/support/c64.60p new file mode 100644 index 0000000..568d8b4 --- /dev/null +++ b/eg/rudiments/support/c64.60p @@ -0,0 +1,6 @@ +// Implementation of `chrout` for the Commodore 64 platform. + +define chrout routine + inputs a + trashes a + @ 65490 diff --git a/eg/rudiments/support/vic20.60p b/eg/rudiments/support/vic20.60p new file mode 100644 index 0000000..15476b6 --- /dev/null +++ b/eg/rudiments/support/vic20.60p @@ -0,0 +1,6 @@ +// Implementation of `chrout` for the Commodore VIC-20 platform. + +define chrout routine + inputs a + trashes a + @ 65490 diff --git a/eg/rudiments/vector-inc.60p b/eg/rudiments/vector-inc.60p deleted file mode 100644 index 1e2bd8e..0000000 --- a/eg/rudiments/vector-inc.60p +++ /dev/null @@ -1,21 +0,0 @@ -// This will not compile on its own, because there is no `main`. -// But this and `vector-main.60p` together will compile. - -define chrout routine - inputs a - trashes a - @ 65490 - -define printa routine - trashes a, z, n -{ - ld a, 65 - call chrout -} - -define printb routine - trashes a, z, n -{ - ld a, 66 - call chrout -} diff --git a/eg/rudiments/vector-main.60p b/eg/rudiments/vector-main.60p deleted file mode 100644 index f8df898..0000000 --- a/eg/rudiments/vector-main.60p +++ /dev/null @@ -1,22 +0,0 @@ -// This will not compile on its own, because `printa` and `printb` are not defined. -// But `vector-inc.60p` and this together will compile. - -vector routine - trashes a, z, n - print - -// routine printb -// trashes a, z, n -// { -// ld a, 66 -// call chrout -// } - -define main routine - trashes print, a, z, n -{ - copy printa, print - call print - copy printb, print - call print -} diff --git a/eg/rudiments/vector-table.60p b/eg/rudiments/vector-table.60p index 6d6e98a..0bead91 100644 --- a/eg/rudiments/vector-table.60p +++ b/eg/rudiments/vector-table.60p @@ -1,7 +1,6 @@ -// // Demonstrates vector tables. -// Prints "AABAB". -// +// Include `support/${PLATFORM}.60p` before this source +// Should print AABAB vector routine trashes a, z, n @@ -11,11 +10,6 @@ vector (routine trashes a, z, n) table[32] vectors -define chrout routine - inputs a - trashes a - @ 65490 - define printa routine trashes a, z, n { diff --git a/eg/rudiments/vector.60p b/eg/rudiments/vector.60p index 7783165..9008745 100644 --- a/eg/rudiments/vector.60p +++ b/eg/rudiments/vector.60p @@ -1,12 +1,10 @@ +// Include `support/${PLATFORM}.60p` before this source +// Should print ??? + vector routine trashes a, z, n print -define chrout routine - inputs a - trashes a - @ 65490 - define printa routine trashes a, z, n { From c85cb6722a773188dadb3342594cbaa4c383c9f1 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 11 Dec 2018 18:14:36 +0000 Subject: [PATCH 24/36] Add local load'n'go script. Fill in what each is expected to write. --- eg/rudiments/README.md | 4 ++++ eg/rudiments/call.60p | 2 +- eg/rudiments/conditional.60p | 2 +- eg/rudiments/conditional2.60p | 2 +- eg/rudiments/goto.60p | 2 +- eg/rudiments/loadngo.sh | 36 +++++++++++++++++++++++++++++++++++ eg/rudiments/loop.60p | 2 +- eg/rudiments/memloc.60p | 2 +- eg/rudiments/vector.60p | 2 +- 9 files changed, 47 insertions(+), 7 deletions(-) create mode 100755 eg/rudiments/loadngo.sh diff --git a/eg/rudiments/README.md b/eg/rudiments/README.md index 032249f..009854e 100644 --- a/eg/rudiments/README.md +++ b/eg/rudiments/README.md @@ -12,6 +12,10 @@ under `platform/`, should be included first, like so that system entry points such as `chrout` are defined. +There's a `loadngo.sh` script in this directory that does this. + + ./loadngo.sh c64 vector-table.60p + `chrout` is a routine with outputs the value of the accumulator as an ASCII character, disturbing none of the other registers, simply for the purposes of producing some observable output. diff --git a/eg/rudiments/call.60p b/eg/rudiments/call.60p index 47eb9ed..eed32f3 100644 --- a/eg/rudiments/call.60p +++ b/eg/rudiments/call.60p @@ -1,5 +1,5 @@ // Include `support/${PLATFORM}.60p` before this source -// Should print ??? +// Should print AA define print routine trashes a, z, n diff --git a/eg/rudiments/conditional.60p b/eg/rudiments/conditional.60p index bf7c637..66c0ea8 100644 --- a/eg/rudiments/conditional.60p +++ b/eg/rudiments/conditional.60p @@ -1,6 +1,6 @@ // Demonstrates vector tables. // Include `support/${PLATFORM}.60p` before this source -// Should print ??? +// Should print YN define main routine trashes a, x, y, z, n, c, v diff --git a/eg/rudiments/conditional2.60p b/eg/rudiments/conditional2.60p index 6c37e4e..0def82c 100644 --- a/eg/rudiments/conditional2.60p +++ b/eg/rudiments/conditional2.60p @@ -1,5 +1,5 @@ // Include `support/${PLATFORM}.60p` before this source -// Should print ??? +// Should print YA define main routine trashes a, x, y, z, n, c, v diff --git a/eg/rudiments/goto.60p b/eg/rudiments/goto.60p index faa0c4e..1c8d9a9 100644 --- a/eg/rudiments/goto.60p +++ b/eg/rudiments/goto.60p @@ -1,5 +1,5 @@ // Include `support/${PLATFORM}.60p` before this source -// Should print ??? +// Should print AB define bar routine trashes a, z, n { ld a, 66 diff --git a/eg/rudiments/loadngo.sh b/eg/rudiments/loadngo.sh new file mode 100755 index 0000000..18eab98 --- /dev/null +++ b/eg/rudiments/loadngo.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +usage="Usage: loadngo.sh (c64|vic20) " + +arch="$1" +shift 1 +if [ "X$arch" = "Xc64" ]; then + output_format='c64-basic-prg' + if [ -e vicerc ]; then + emu="x64 -config vicerc" + else + emu="x64" + fi +elif [ "X$arch" = "Xvic20" ]; then + output_format='vic20-basic-prg' + if [ -e vicerc ]; then + emu="xvic -config vicerc" + else + emu="xvic" + fi +else + echo $usage && exit 1 +fi + +src="$1" +if [ "X$src" = "X" ]; then + echo $usage && exit 1 +fi + +### do it ### + +out=/tmp/a-out.prg +../../bin/sixtypical --traceback --output-format=$output_format support/$arch.60p $src --output $out || exit 1 +ls -la $out +$emu $out +rm -f $out diff --git a/eg/rudiments/loop.60p b/eg/rudiments/loop.60p index 40598b1..6ccb445 100644 --- a/eg/rudiments/loop.60p +++ b/eg/rudiments/loop.60p @@ -1,5 +1,5 @@ // Include `support/${PLATFORM}.60p` before this source -// Should print ??? +// Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ define main routine trashes a, y, z, n, c diff --git a/eg/rudiments/memloc.60p b/eg/rudiments/memloc.60p index 2a255d5..cb8bd5b 100644 --- a/eg/rudiments/memloc.60p +++ b/eg/rudiments/memloc.60p @@ -1,5 +1,5 @@ // Include `support/${PLATFORM}.60p` before this source -// Should print ??? +// Should print AB byte foo diff --git a/eg/rudiments/vector.60p b/eg/rudiments/vector.60p index 9008745..b4b018c 100644 --- a/eg/rudiments/vector.60p +++ b/eg/rudiments/vector.60p @@ -1,5 +1,5 @@ // Include `support/${PLATFORM}.60p` before this source -// Should print ??? +// Should print AB vector routine trashes a, z, n From d86612acceae9a0005e32c926164350b909b316c Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 11 Dec 2018 19:11:03 +0000 Subject: [PATCH 25/36] Note what we've done with the example programs. --- HISTORY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index dda7e4c..24db63e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -13,6 +13,8 @@ History of SixtyPical of `word` memory locations (at the cost of trashing `a`.) * Fixed pathological memory use in the lexical scanner - should be much less inefficient now when parsing large source files. +* Reorganized the examples in `eg/rudiments/` to make them + officially platform-agnostic and to state the expected output. 0.17 ---- From d13c6a94a26637c3a6b9a73496dad7947627cf40 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 11 Dec 2018 19:27:40 +0000 Subject: [PATCH 26/36] Drop the check for "consistent initialization" inside `if` blocks. --- HISTORY.md | 3 +++ TODO.md | 9 ------- eg/c64/demo-game/demo-game.60p | 15 ------------ src/sixtypical/analyzer.py | 26 ++++---------------- tests/SixtyPical Analysis.md | 44 ++++++++++++++++++++++++++++++---- 5 files changed, 46 insertions(+), 51 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 24db63e..34e6b0d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,9 @@ History of SixtyPical 0.18 ---- +* The "consistent initialization" check inside `if` blocks has + been dropped. If a location is initialized inside one block + but not the other, it is treated as uninitialized afterwards. * Syntactically, `goto` may only appear at the end of a block. It need no longer be the final instruction in a routine, as long as the type context is consistent at every exit. diff --git a/TODO.md b/TODO.md index 69227f8..6e7857e 100644 --- a/TODO.md +++ b/TODO.md @@ -44,15 +44,6 @@ buffer; and the ones you establish must be disjoint. An alternative would be `static` pointers, which are currently not possible because pointers must be zero-page, thus `@`, thus uninitialized. -### Question "consistent initialization" - -Question the value of the "consistent initialization" principle for `if` statement analysis. - -Part of this is the trashes at the end; I think what it should be is that the trashes -after the `if` is the union of the trashes in each of the branches; this would obviate the -need to `trash` values explicitly, but if you tried to access them afterwards, it would still -error. - ### Tail-call optimization If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, diff --git a/eg/c64/demo-game/demo-game.60p b/eg/c64/demo-game/demo-game.60p index 4097f70..1e6fbd6 100644 --- a/eg/c64/demo-game/demo-game.60p +++ b/eg/c64/demo-game/demo-game.60p @@ -311,14 +311,6 @@ define player_logic logic_routine ld a, 1 st a, player_died } - - // FIXME these trashes, strictly speaking, probably shouldn't be needed, - // but currently the compiler cares a little too much about values that are - // initialized in one branch of an `if`, but not the other, but are trashed - // at the end of the routine anyway. - trash ptr - trash y - trash v } } @@ -355,13 +347,6 @@ define enemy_logic logic_routine add ptr, pos copy 82, [ptr] + y } - - // FIXME these trashes, strictly speaking, probably shouldn't be needed, - // but currently the compiler cares too much about values that are - // initialized in one branch of an `if`, but not the other, but trashed - // at the end of the routine anyway. - trash ptr - trash y } else { copy delta, compare_target st on, c diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 9837424..d1006c6 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -414,11 +414,11 @@ class Analyzer(object): if self.debug: print("at end of routine `{}`:".format(routine.name)) print(context) - print("trashed: ", LocationRef.format_set(trashed)) + #print("trashed: ", LocationRef.format_set(trashed)) print("outputs: ", LocationRef.format_set(type_.outputs)) - trashed_outputs = type_.outputs & trashed - if trashed_outputs: - print("TRASHED OUTPUTS: ", LocationRef.format_set(trashed_outputs)) + #trashed_outputs = type_.outputs & trashed + #if trashed_outputs: + # print("TRASHED OUTPUTS: ", LocationRef.format_set(trashed_outputs)) print('') print('-' * 79) print('') @@ -801,24 +801,6 @@ class Analyzer(object): 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(instr.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(instr.src) - ) - # merge the contexts. # first, the easy case: if one of the contexts has terminated, just use the other one. diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 0331e7e..968521f 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1607,7 +1607,9 @@ Both blocks of an `if` are analyzed. | } = ok -If a location is initialized in one block, is must be initialized in the other as well. +If a location is initialized in one block, is 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 @@ -1621,7 +1623,7 @@ If a location is initialized in one block, is must be initialized in the other a | ld a, 23 | } | } - ? InconsistentInitializationError: x + ? UnmeaningfulOutputError: x | define foo routine | inputs a @@ -1635,7 +1637,7 @@ If a location is initialized in one block, is must be initialized in the other a | ld x, 7 | } | } - ? InconsistentInitializationError: x + ? UnmeaningfulOutputError: x | define foo routine | inputs a @@ -1649,7 +1651,39 @@ If a location is initialized in one block, is must be initialized in the other a | ld x, 7 | } | } - ? InconsistentInitializationError: x + ? UnmeaningfulOutputError: 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 @@ -1698,7 +1732,7 @@ An `if` with a single block is analyzed as if it had an empty `else` block. | ld x, 7 | } | } - ? InconsistentInitializationError: x + ? UnmeaningfulOutputError: x | define foo routine | inputs a From 547b7c960a3af1b10b3ae9108a5311a6cfd2ed12 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 11 Dec 2018 22:40:23 +0000 Subject: [PATCH 27/36] Restore the debugging (which should be rethought, anyway). --- src/sixtypical/analyzer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index d1006c6..2298e53 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -411,14 +411,16 @@ class Analyzer(object): self.analyze_block(routine.block, context) + trashed = set(context.each_touched()) - set(context.each_meaningful()) + if self.debug: print("at end of routine `{}`:".format(routine.name)) print(context) - #print("trashed: ", LocationRef.format_set(trashed)) + print("trashed: ", LocationRef.format_set(trashed)) print("outputs: ", LocationRef.format_set(type_.outputs)) - #trashed_outputs = type_.outputs & trashed - #if trashed_outputs: - # print("TRASHED OUTPUTS: ", LocationRef.format_set(trashed_outputs)) + trashed_outputs = type_.outputs & trashed + if trashed_outputs: + print("TRASHED OUTPUTS: ", LocationRef.format_set(trashed_outputs)) print('') print('-' * 79) print('') @@ -438,8 +440,6 @@ class Analyzer(object): raise InconsistentExitError("Exit contexts are not consistent") context.update_from(exit_context) - trashed = set(context.each_touched()) - set(context.each_meaningful()) - # these all apply whether we encountered goto(s) in this routine, or not...: # can't trash an output. From 538365f1e199140ccf729238a45e1c6da95d49a3 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 11 Dec 2018 22:42:59 +0000 Subject: [PATCH 28/36] Add one more test case, to demonstrate that it's not just output. --- tests/SixtyPical Analysis.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 968521f..a86d548 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1607,7 +1607,7 @@ Both blocks of an `if` are analyzed. | } = ok -If a location is initialized in one block, is must be initialized in the other as well +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. @@ -1653,6 +1653,20 @@ it will be considered uninitialized. | } ? 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 From 97e6e619fff121b1427ed4d66ec63ee9bbc6eb36 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 12 Dec 2018 09:01:46 +0000 Subject: [PATCH 29/36] `cmp` can compare against a literal word. --- HISTORY.md | 3 ++- src/sixtypical/compiler.py | 10 ++++++++++ tests/SixtyPical Analysis.md | 22 ++++++++++++++++++++++ tests/SixtyPical Compilation.md | 16 +++++++++++----- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 24db63e..4ebbea9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,7 +10,8 @@ History of SixtyPical * When the range of a location is known, `inc` and `dec` on it will usually shift the known instead of invalidating it. * `cmp` instruction can now perform a 16-bit unsigned comparison - of `word` memory locations (at the cost of trashing `a`.) + of `word` memory locations and `word` literals (at the cost of + trashing the `a` register.) * Fixed pathological memory use in the lexical scanner - should be much less inefficient now when parsing large source files. * Reorganized the examples in `eg/rudiments/` to make them diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index a2b983d..2675b54 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -402,6 +402,16 @@ class Compiler(object): self.emitter.emit(CMP(Absolute(Offset(src_label, 1)))) self.emitter.resolve_label(end_label) return + if isinstance(src, ConstantRef) and src.type == TYPE_WORD: + dest_label = self.get_label(dest.name) + self.emitter.emit(LDA(Absolute(dest_label))) + self.emitter.emit(CMP(Immediate(Byte(src.high_byte())))) + end_label = Label('end_label') + self.emitter.emit(BNE(Relative(end_label))) + self.emitter.emit(LDA(Absolute(Offset(dest_label, 1)))) + self.emitter.emit(CMP(Immediate(Byte(src.low_byte())))) + self.emitter.resolve_label(end_label) + return cls = { 'a': CMP, 'x': CPX, diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 0331e7e..23baa18 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1207,6 +1207,28 @@ Some rudimentary tests for `cmp`. | } ? UnmeaningfulReadError: zb +`cmp` can compare against a literal word. + + | word za + | + | define main routine + | inputs za + | trashes a, z, c, n + | { + | cmp za, 4000 + | } + = ok + + | word za + | + | define main routine + | inputs za + | trashes z, c, n + | { + | cmp za, 4000 + | } + ? ForbiddenWriteError: a + ### and ### Some rudimentary tests for `and`. diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index d61ef30..87e427a 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -395,15 +395,21 @@ Compiling 16-bit `cmp`. | trashes a, z, c, n | { | cmp za, zb + | cmp za, 4000 | } = $080D LDA $EA61 - = $0810 CMP $081C + = $0810 CMP $0828 = $0813 BNE $081B = $0815 LDA $EA62 - = $0818 CMP $081D - = $081B RTS - = $081C .byte $BB - = $081D .byte $0B + = $0818 CMP $0829 + = $081B LDA $EA61 + = $081E CMP #$0F + = $0820 BNE $0827 + = $0822 LDA $EA62 + = $0825 CMP #$A0 + = $0827 RTS + = $0828 .byte $BB + = $0829 .byte $0B Compiling `if`. From d1a29709f2ea3a99d6d11221f0f57593d958f161 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 12 Dec 2018 09:09:45 +0000 Subject: [PATCH 30/36] Add example test program for cmp-against-literal-word. Fix it. --- eg/rudiments/cmp-litword.60p | 64 +++++++++++++++++++++++++++++++++ src/sixtypical/compiler.py | 4 +-- tests/SixtyPical Compilation.md | 4 +-- 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 eg/rudiments/cmp-litword.60p diff --git a/eg/rudiments/cmp-litword.60p b/eg/rudiments/cmp-litword.60p new file mode 100644 index 0000000..5cf9793 --- /dev/null +++ b/eg/rudiments/cmp-litword.60p @@ -0,0 +1,64 @@ +// Include `support/${PLATFORM}.60p` before this source +// Should print ENGGL + +word w1 + +define main routine + outputs w1 + trashes a, x, y, z, n, c, v +{ + copy 4000, w1 + + cmp w1, 4000 + if z { + ld a, 69 // E + call chrout + } else { + ld a, 78 // N + call chrout + } + + copy 4000, w1 + + cmp w1, 4001 + if z { + ld a, 69 // E + call chrout + } else { + ld a, 78 // N + call chrout + } + + copy 20002, w1 + + cmp w1, 20001 // 20002 >= 20001 + if c { + ld a, 71 // G + call chrout + } else { + ld a, 76 // L + call chrout + } + + copy 20001, w1 + + cmp w1, 20001 // 20001 >= 20001 + if c { + ld a, 71 // G + call chrout + } else { + ld a, 76 // L + call chrout + } + + copy 20000, w1 + + cmp w1, 20001 // 20000 < 20001 + if c { + ld a, 71 // G + call chrout + } else { + ld a, 76 // L + call chrout + } +} diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 2675b54..3659014 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -405,11 +405,11 @@ class Compiler(object): if isinstance(src, ConstantRef) and src.type == TYPE_WORD: dest_label = self.get_label(dest.name) self.emitter.emit(LDA(Absolute(dest_label))) - self.emitter.emit(CMP(Immediate(Byte(src.high_byte())))) + self.emitter.emit(CMP(Immediate(Byte(src.low_byte())))) end_label = Label('end_label') self.emitter.emit(BNE(Relative(end_label))) self.emitter.emit(LDA(Absolute(Offset(dest_label, 1)))) - self.emitter.emit(CMP(Immediate(Byte(src.low_byte())))) + self.emitter.emit(CMP(Immediate(Byte(src.high_byte())))) self.emitter.resolve_label(end_label) return cls = { diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 87e427a..e00f954 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -403,10 +403,10 @@ Compiling 16-bit `cmp`. = $0815 LDA $EA62 = $0818 CMP $0829 = $081B LDA $EA61 - = $081E CMP #$0F + = $081E CMP #$A0 = $0820 BNE $0827 = $0822 LDA $EA62 - = $0825 CMP #$A0 + = $0825 CMP #$0F = $0827 RTS = $0828 .byte $BB = $0829 .byte $0B From 03a682ff08c4787bd6ec4b5a8fe574cb21083507 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 12 Dec 2018 09:13:13 +0000 Subject: [PATCH 31/36] Make word-table print YY. --- eg/rudiments/word-table.60p | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/eg/rudiments/word-table.60p b/eg/rudiments/word-table.60p index ffad04a..54babbc 100644 --- a/eg/rudiments/word-table.60p +++ b/eg/rudiments/word-table.60p @@ -1,16 +1,41 @@ +// Include `support/${PLATFORM}.60p` before this source +// Should print YY + word one word table[256] many define main routine inputs one, many outputs one, many - trashes a, x, y, n, z + trashes a, x, y, c, n, z { ld x, 0 - ld y, 0 + ld y, 1 copy 777, one copy one, many + x + copy 888, one copy one, many + y + + ld x, 1 + ld y, 0 + copy many + x, one + cmp one, 888 + if z { + ld a, 89 + call chrout + } else { + ld a, 78 + call chrout + } + copy many + y, one + cmp one, 777 + if z { + ld a, 89 + call chrout + } else { + ld a, 78 + call chrout + } } From 0ec8970c766cb6761da1ab2e7c919e780d3133a1 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 12 Dec 2018 09:27:25 +0000 Subject: [PATCH 32/36] Expand example with code that will likely become library support. --- eg/rudiments/example.60p | 47 ++++++++++++++++++++++++++++++++-------- eg/rudiments/if.60p | 12 ---------- 2 files changed, 38 insertions(+), 21 deletions(-) delete mode 100644 eg/rudiments/if.60p diff --git a/eg/rudiments/example.60p b/eg/rudiments/example.60p index c4475da..9eefa8e 100644 --- a/eg/rudiments/example.60p +++ b/eg/rudiments/example.60p @@ -1,14 +1,43 @@ byte lives +byte table[16] hexchars : "0123456789ABCDEF" + +define prbyte routine + inputs a, hexchars + trashes a, z, n, c, v +{ + save x { + save a { + st off, c + shr a + shr a + shr a + shr a + and a, 15 + ld x, a + ld a, hexchars + x + call chrout + } + save a { + and a, 15 + ld x, a + ld a, hexchars + x + call chrout + } + } +} + define main routine - inputs lives + inputs lives, hexchars outputs lives trashes a, x, z, n, c, v - { - ld a, 0 - st a, lives - ld x, lives - st off, c - add x, 1 - st x, lives - } +{ + ld a, 0 + st a, lives + ld x, lives + st off, c + inc x + st x, lives + ld a, lives + call prbyte +} diff --git a/eg/rudiments/if.60p b/eg/rudiments/if.60p deleted file mode 100644 index 9b0b968..0000000 --- a/eg/rudiments/if.60p +++ /dev/null @@ -1,12 +0,0 @@ -define main routine - inputs a - outputs a - trashes z, n, c -{ - cmp a, 42 - if z { - ld a, 7 - } else { - ld a, 23 - } -} From 2803aa2d056f39c17e3c52bccea85313763e7f52 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 12 Dec 2018 10:11:16 +0000 Subject: [PATCH 33/36] Add failing tests for `add` and `sub` on locations other than `a`. --- tests/SixtyPical Analysis.md | 52 +++++++++++++++++++++++++++++++++ tests/SixtyPical Compilation.md | 31 ++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 23baa18..7990324 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -852,6 +852,32 @@ Can't `add` to a memory location that isn't writeable. | } ? ForbiddenWriteError: a +You can `add` a byte constant to a byte memory location. + + | byte lives + | define main routine + | inputs a, lives + | outputs lives + | trashes a, c, z, v, n + | { + | st off, c + | add lives, 3 + | } + = ok + +`add`ing a byte constant to a byte memory location trashes `a`. + + | byte lives + | define main routine + | inputs a, lives + | outputs lives + | trashes c, z, v, n + | { + | st off, c + | add lives, 3 + | } + ? UnmeaningfulOutputError: a + You can `add` a word constant to a word memory location. | word score @@ -996,6 +1022,32 @@ Can't `sub` to a memory location that isn't writeable. | } ? ForbiddenWriteError: a +You can `sub` a byte constant from a byte memory location. + + | byte lives + | define main routine + | inputs a, lives + | outputs lives + | trashes a, c, z, v, n + | { + | st on, c + | sub lives, 3 + | } + = ok + +`sub`ing a byte constant from a byte memory location trashes `a`. + + | byte lives + | define main routine + | inputs a, lives + | outputs lives + | trashes c, z, v, n + | { + | st on, c + | sub lives, 3 + | } + ? UnmeaningfulOutputError: a + You can `sub` a word constant from a word memory location. | word score diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index e00f954..1d25e49 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -1025,6 +1025,37 @@ Copying to and from a vector table. = $0842 JMP ($0846) = $0845 RTS +### add, sub + +Various modes of `add`. + + | byte lives + | word score + | define main routine + | inputs lives, score + | outputs lives, score + | trashes a, x, y, c, z, v, n + | { + | ld a, 0 + | ld x, 0 + | ld y, 0 + | st off, c + | add a, 7 + | add a, lives + | // add x, 7 + | // add y, 7 + | add lives, 2 + | add score, 1999 + | } + = $080D CLC + = $080E LDA $081F + = $0811 ADC #$CF + = $0813 STA $081F + = $0816 LDA $0820 + = $0819 ADC #$07 + = $081B STA $0820 + = $081E RTS + ### word operations Adding a constant word to a word memory location. From 35a1053439adce04c21cdba0bba94f9b7cdfe379 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 12 Dec 2018 10:34:25 +0000 Subject: [PATCH 34/36] Support more modes on `add` and `sub`. --- HISTORY.md | 2 + doc/SixtyPical.md | 12 ++++- src/sixtypical/analyzer.py | 6 +++ src/sixtypical/compiler.py | 32 ++++++++++++ tests/SixtyPical Analysis.md | 58 ++++++++++++++++++++- tests/SixtyPical Compilation.md | 89 +++++++++++++++++++++++++++++---- 6 files changed, 184 insertions(+), 15 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 4ebbea9..03db443 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -12,6 +12,8 @@ History of SixtyPical * `cmp` instruction can now perform a 16-bit unsigned comparison of `word` memory locations and `word` literals (at the cost of trashing the `a` register.) +* `add` (resp. `sub`) now support adding (resp. subtracting) a + byte location or a byte literal from a byte location. * Fixed pathological memory use in the lexical scanner - should be much less inefficient now when parsing large source files. * Reorganized the examples in `eg/rudiments/` to make them diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index a4b4d97..d9b177d 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -333,8 +333,9 @@ this mode is used. Adds the contents of src to dest and stores the result in dest. -* It is illegal if src OR dest OR c is uninitialized. +* It is illegal if src OR dest OR `c` is uninitialized. * It is illegal if dest is read-only. +* It is illegal if dest is `x` or `y`. * It is illegal if dest does not occur in the WRITES of the current routine. Affects n, z, c, and v flags, requiring that they be in the WRITES, @@ -345,6 +346,9 @@ dest and src continue to be initialized afterwards. In addition, if dest is of `word` type, then src must also be of `word` type, and in this case this instruction trashes the `a` register. +In fact, this instruction trashes the `a` register in all cases except +when the dest is `a`. + NOTE: If dest is a pointer, the addition does not check if the result of the pointer arithmetic continues to be valid (within a buffer) or not. @@ -367,8 +371,9 @@ and initializing them afterwards. Subtracts the contents of src from dest and stores the result in dest. -* It is illegal if src OR dest OR c is uninitialized. +* It is illegal if src OR dest OR `c` is uninitialized. * It is illegal if dest is read-only. +* It is illegal if dest is `x` or `y`. * It is illegal if dest does not occur in the WRITES of the current routine. Affects n, z, c, and v flags, requiring that they be in the WRITES, @@ -379,6 +384,9 @@ dest and src continue to be initialized afterwards. In addition, if dest is of `word` type, then src must also be of `word` type, and in this case this instruction trashes the `a` register. +In fact, this instruction trashes the `a` register in all cases except +when the dest is `a`. + ### dec ### dec diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 9837424..a12680c 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -533,6 +533,9 @@ class Analyzer(object): context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) elif src.type == TYPE_BYTE: self.assert_type(TYPE_BYTE, src, dest) + if dest != REG_A: + context.set_touched(REG_A) + context.set_unmeaningful(REG_A) else: self.assert_type(TYPE_WORD, src) if dest.type == TYPE_WORD: @@ -551,6 +554,9 @@ class Analyzer(object): context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) elif src.type == TYPE_BYTE: self.assert_type(TYPE_BYTE, src, dest) + if dest != REG_A: + context.set_touched(REG_A) + context.set_unmeaningful(REG_A) else: self.assert_type(TYPE_WORD, src, dest) context.set_touched(REG_A) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 3659014..fe1e1c5 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -244,6 +244,8 @@ class Compiler(object): raise UnsupportedOpcodeError(instr) self.emitter.emit(op_cls(operand)) elif opcode == 'add': + if dest == REG_X or dest == REG_Y: + raise UnsupportedOpcodeError(instr) if dest == REG_A: if isinstance(src, ConstantRef): self.emitter.emit(ADC(Immediate(Byte(src.value)))) @@ -251,6 +253,20 @@ class Compiler(object): self.emitter.emit(ADC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) else: self.emitter.emit(ADC(Absolute(self.get_label(src.name)))) + elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE: + if isinstance(src, ConstantRef): + dest_label = self.get_label(dest.name) + self.emitter.emit(LDA(Absolute(dest_label))) + self.emitter.emit(ADC(Immediate(Byte(src.low_byte())))) + self.emitter.emit(STA(Absolute(dest_label))) + elif isinstance(src, LocationRef): + src_label = self.get_label(src.name) + dest_label = self.get_label(dest.name) + self.emitter.emit(LDA(Absolute(dest_label))) + self.emitter.emit(ADC(Absolute(src_label))) + self.emitter.emit(STA(Absolute(dest_label))) + else: + raise UnsupportedOpcodeError(instr) elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: if isinstance(src, ConstantRef): dest_label = self.get_label(dest.name) @@ -294,6 +310,8 @@ class Compiler(object): else: raise UnsupportedOpcodeError(instr) elif opcode == 'sub': + if dest == REG_X or dest == REG_Y: + raise UnsupportedOpcodeError(instr) if dest == REG_A: if isinstance(src, ConstantRef): self.emitter.emit(SBC(Immediate(Byte(src.value)))) @@ -301,6 +319,20 @@ class Compiler(object): self.emitter.emit(SBC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) else: self.emitter.emit(SBC(Absolute(self.get_label(src.name)))) + elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE: + if isinstance(src, ConstantRef): + dest_label = self.get_label(dest.name) + self.emitter.emit(LDA(Absolute(dest_label))) + self.emitter.emit(SBC(Immediate(Byte(src.low_byte())))) + self.emitter.emit(STA(Absolute(dest_label))) + elif isinstance(src, LocationRef): + src_label = self.get_label(src.name) + dest_label = self.get_label(dest.name) + self.emitter.emit(LDA(Absolute(dest_label))) + self.emitter.emit(SBC(Absolute(src_label))) + self.emitter.emit(STA(Absolute(dest_label))) + else: + raise UnsupportedOpcodeError(instr) elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: if isinstance(src, ConstantRef): dest_label = self.get_label(dest.name) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 7990324..6d645d6 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -870,7 +870,7 @@ You can `add` a byte constant to a byte memory location. | byte lives | define main routine | inputs a, lives - | outputs lives + | outputs a, lives | trashes c, z, v, n | { | st off, c @@ -878,6 +878,33 @@ You can `add` a byte constant to a byte memory location. | } ? UnmeaningfulOutputError: a +You can `add` a byte memory location to another byte memory location. +This trashes `a`. + + | byte lives + | byte extra + | define main routine + | inputs a, lives, extra + | outputs lives + | trashes a, c, z, v, n + | { + | st off, c + | add lives, extra + | } + = ok + + | byte lives + | byte extra + | define main routine + | inputs a, lives, extra + | outputs a, lives + | trashes c, z, v, n + | { + | st off, c + | add lives, extra + | } + ? UnmeaningfulOutputError: a + You can `add` a word constant to a word memory location. | word score @@ -1040,7 +1067,7 @@ You can `sub` a byte constant from a byte memory location. | byte lives | define main routine | inputs a, lives - | outputs lives + | outputs a, lives | trashes c, z, v, n | { | st on, c @@ -1048,6 +1075,33 @@ You can `sub` a byte constant from a byte memory location. | } ? UnmeaningfulOutputError: a +You can `sub` a byte memory location from another byte memory location. +This trashes `a`. + + | byte lives + | byte extra + | define main routine + | inputs a, lives, extra + | outputs lives + | trashes a, c, z, v, n + | { + | st on, c + | sub lives, extra + | } + = ok + + | byte lives + | byte extra + | define main routine + | inputs a, lives, extra + | outputs a, lives + | trashes c, z, v, n + | { + | st on, c + | sub lives, extra + | } + ? UnmeaningfulOutputError: a + You can `sub` a word constant from a word memory location. | word score diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 1d25e49..c3a4ec7 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -1030,9 +1030,11 @@ Copying to and from a vector table. Various modes of `add`. | byte lives + | byte extra | word score + | word bonus | define main routine - | inputs lives, score + | inputs lives, score, extra, bonus | outputs lives, score | trashes a, x, y, c, z, v, n | { @@ -1042,19 +1044,84 @@ Various modes of `add`. | st off, c | add a, 7 | add a, lives - | // add x, 7 - | // add y, 7 | add lives, 2 + | add lives, extra | add score, 1999 + | add score, bonus | } - = $080D CLC - = $080E LDA $081F - = $0811 ADC #$CF - = $0813 STA $081F - = $0816 LDA $0820 - = $0819 ADC #$07 - = $081B STA $0820 - = $081E RTS + = $080D LDA #$00 + = $080F LDX #$00 + = $0811 LDY #$00 + = $0813 CLC + = $0814 ADC #$07 + = $0816 ADC $084D + = $0819 LDA $084D + = $081C ADC #$02 + = $081E STA $084D + = $0821 LDA $084D + = $0824 ADC $084E + = $0827 STA $084D + = $082A LDA $084F + = $082D ADC #$CF + = $082F STA $084F + = $0832 LDA $0850 + = $0835 ADC #$07 + = $0837 STA $0850 + = $083A LDA $084F + = $083D ADC $0851 + = $0840 STA $084F + = $0843 LDA $0850 + = $0846 ADC $0852 + = $0849 STA $0850 + = $084C RTS + +Various modes of `sub`. + + | byte lives + | byte extra + | word score + | word bonus + | define main routine + | inputs lives, score, extra, bonus + | outputs lives, score + | trashes a, x, y, c, z, v, n + | { + | ld a, 0 + | ld x, 0 + | ld y, 0 + | st on, c + | sub a, 7 + | sub a, lives + | sub lives, 2 + | sub lives, extra + | sub score, 1999 + | sub score, bonus + | } + = $080D LDA #$00 + = $080F LDX #$00 + = $0811 LDY #$00 + = $0813 SEC + = $0814 SBC #$07 + = $0816 SBC $084D + = $0819 LDA $084D + = $081C SBC #$02 + = $081E STA $084D + = $0821 LDA $084D + = $0824 SBC $084E + = $0827 STA $084D + = $082A LDA $084F + = $082D SBC #$CF + = $082F STA $084F + = $0832 LDA $0850 + = $0835 SBC #$07 + = $0837 STA $0850 + = $083A LDA $084F + = $083D SBC $0851 + = $0840 STA $084F + = $0843 LDA $0850 + = $0846 SBC $0852 + = $0849 STA $0850 + = $084C RTS ### word operations From f2570729a407ca65222d873085071851bf21fa18 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 12 Dec 2018 15:27:57 +0000 Subject: [PATCH 35/36] Final (for now) cleanup of the example sources. --- eg/rudiments/add-pass.60p | 8 -------- eg/rudiments/add-word.60p | 9 --------- eg/rudiments/add.60p | 36 ++++++++++++++++++++++++++++++++++++ eg/rudiments/buffer.60p | 17 ++++++++++++++++- eg/rudiments/copy.60p | 10 ---------- eg/rudiments/example.60p | 3 +++ eg/rudiments/forever.60p | 3 +++ 7 files changed, 58 insertions(+), 28 deletions(-) delete mode 100644 eg/rudiments/add-pass.60p delete mode 100644 eg/rudiments/add-word.60p create mode 100644 eg/rudiments/add.60p delete mode 100644 eg/rudiments/copy.60p diff --git a/eg/rudiments/add-pass.60p b/eg/rudiments/add-pass.60p deleted file mode 100644 index 4427cbc..0000000 --- a/eg/rudiments/add-pass.60p +++ /dev/null @@ -1,8 +0,0 @@ -define main routine - inputs a - outputs a - trashes c, z, n, v -{ - st off, c - add a, 4 -} diff --git a/eg/rudiments/add-word.60p b/eg/rudiments/add-word.60p deleted file mode 100644 index a3c1d86..0000000 --- a/eg/rudiments/add-word.60p +++ /dev/null @@ -1,9 +0,0 @@ -word score -define main routine - inputs score - outputs score - trashes a, c, z, v, n -{ - st off, c - add score, 1999 -} diff --git a/eg/rudiments/add.60p b/eg/rudiments/add.60p new file mode 100644 index 0000000..3c5a631 --- /dev/null +++ b/eg/rudiments/add.60p @@ -0,0 +1,36 @@ +// Include `support/${PLATFORM}.60p` before this source +// Should print YY + +word score + +define main routine + inputs a, score + outputs score + trashes a, c, z, n, v +{ + ld a, 3 + st off, c + add a, 4 + + cmp a, 7 + if z { + ld a, 89 + call chrout + } else { + ld a, 78 + call chrout + } + + copy 999, score + st off, c + add score, 1999 + + cmp score, 2998 + if z { + ld a, 89 + call chrout + } else { + ld a, 78 + call chrout + } +} diff --git a/eg/rudiments/buffer.60p b/eg/rudiments/buffer.60p index 79bc512..7772449 100644 --- a/eg/rudiments/buffer.60p +++ b/eg/rudiments/buffer.60p @@ -1,3 +1,6 @@ +// Include `support/${PLATFORM}.60p` before this source +// Should print Y + buffer[2048] buf pointer ptr @ 254 byte foo @@ -5,11 +8,23 @@ byte foo define main routine inputs buf outputs buf, y, foo - trashes a, z, n, ptr + trashes a, z, n, c, ptr { ld y, 0 copy ^buf, ptr copy 123, [ptr] + y copy [ptr] + y, foo copy foo, [ptr] + y + + // TODO: support saying `cmp foo, 123`, maybe + ld a, foo + cmp a, 123 + + if z { + ld a, 89 + call chrout + } else { + ld a, 78 + call chrout + } } diff --git a/eg/rudiments/copy.60p b/eg/rudiments/copy.60p deleted file mode 100644 index d31fd6b..0000000 --- a/eg/rudiments/copy.60p +++ /dev/null @@ -1,10 +0,0 @@ -byte bar -byte baz - -define main routine - inputs baz - outputs bar - trashes a, n, z -{ - copy baz, bar -} diff --git a/eg/rudiments/example.60p b/eg/rudiments/example.60p index 9eefa8e..8b27de6 100644 --- a/eg/rudiments/example.60p +++ b/eg/rudiments/example.60p @@ -1,3 +1,6 @@ +// Include `support/${PLATFORM}.60p` before this source +// Should print 01 + byte lives byte table[16] hexchars : "0123456789ABCDEF" diff --git a/eg/rudiments/forever.60p b/eg/rudiments/forever.60p index a9968cf..4d9bc92 100644 --- a/eg/rudiments/forever.60p +++ b/eg/rudiments/forever.60p @@ -1,3 +1,6 @@ +// This program is expected to loop forever. +// Be prepared to forcibly terminate your emulator. + define main routine trashes a, y, z, n, c { From 49bb5b578aa306c537328274ece7e368fd0421e3 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 25 Dec 2018 18:10:58 +0000 Subject: [PATCH 36/36] Extract utility routine to "support/stdlib.60p" source. --- eg/rudiments/example.60p | 29 +---------------------------- eg/rudiments/loadngo.sh | 2 +- eg/rudiments/support/stdlib.60p | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 29 deletions(-) create mode 100644 eg/rudiments/support/stdlib.60p diff --git a/eg/rudiments/example.60p b/eg/rudiments/example.60p index 8b27de6..a374c6e 100644 --- a/eg/rudiments/example.60p +++ b/eg/rudiments/example.60p @@ -1,35 +1,8 @@ -// Include `support/${PLATFORM}.60p` before this source +// Include `support/${PLATFORM}.60p` and `support/stdlib.60p` before this source // Should print 01 byte lives -byte table[16] hexchars : "0123456789ABCDEF" - -define prbyte routine - inputs a, hexchars - trashes a, z, n, c, v -{ - save x { - save a { - st off, c - shr a - shr a - shr a - shr a - and a, 15 - ld x, a - ld a, hexchars + x - call chrout - } - save a { - and a, 15 - ld x, a - ld a, hexchars + x - call chrout - } - } -} - define main routine inputs lives, hexchars outputs lives diff --git a/eg/rudiments/loadngo.sh b/eg/rudiments/loadngo.sh index 18eab98..8fdb811 100755 --- a/eg/rudiments/loadngo.sh +++ b/eg/rudiments/loadngo.sh @@ -30,7 +30,7 @@ fi ### do it ### out=/tmp/a-out.prg -../../bin/sixtypical --traceback --output-format=$output_format support/$arch.60p $src --output $out || exit 1 +../../bin/sixtypical --traceback --output-format=$output_format support/$arch.60p support/stdlib.60p $src --output $out || exit 1 ls -la $out $emu $out rm -f $out diff --git a/eg/rudiments/support/stdlib.60p b/eg/rudiments/support/stdlib.60p new file mode 100644 index 0000000..548f030 --- /dev/null +++ b/eg/rudiments/support/stdlib.60p @@ -0,0 +1,26 @@ +byte table[16] hexchars : "0123456789ABCDEF" + +define prbyte routine + inputs a, hexchars + trashes a, z, n, c, v +{ + save x { + save a { + st off, c + shr a + shr a + shr a + shr a + and a, 15 + ld x, a + ld a, hexchars + x + call chrout + } + save a { + and a, 15 + ld x, a + ld a, hexchars + x + call chrout + } + } +}