git @ Cat's Eye Technologies SixtyPical / 0.18
Merge pull request #19 from catseye/develop-0.18 Develop 0.18 Chris Pressey authored 4 years ago GitHub committed 4 years ago
48 changed file(s) with 1401 addition(s) and 318 deletion(s). Raw diff Collapse all Expand all
00 History of SixtyPical
11 =====================
2
3 0.18
4 ----
5
6 * The "consistent initialization" check inside `if` blocks has
7 been dropped. If a location is initialized inside one block
8 but not the other, it is treated as uninitialized afterwards.
9 * Syntactically, `goto` may only appear at the end of a block.
10 It need no longer be the final instruction in a routine,
11 as long as the type context is consistent at every exit.
12 * When the range of a location is known, `inc` and `dec`
13 on it will usually shift the known instead of invalidating it.
14 * `cmp` instruction can now perform a 16-bit unsigned comparison
15 of `word` memory locations and `word` literals (at the cost of
16 trashing the `a` register.)
17 * `add` (resp. `sub`) now support adding (resp. subtracting) a
18 byte location or a byte literal from a byte location.
19 * Fixed pathological memory use in the lexical scanner - should
20 be much less inefficient now when parsing large source files.
21 * Reorganized the examples in `eg/rudiments/` to make them
22 officially platform-agnostic and to state the expected output.
223
324 0.17
425 ----
00 SixtyPical
11 ==========
22
3 _Version 0.17. Work-in-progress, everything is subject to change._
3 _Version 0.18. Work-in-progress, everything is subject to change._
44
55 **SixtyPical** is a low-level programming language with advanced
66 static analysis. Many of its primitive instructions resemble
00 TODO for SixtyPical
11 ===================
2
3 ### 16-bit `cmp`
4
5 This is because we don't actually want `low` and `high` address operators
6 that turn `word` type into `byte`.
7
8 This is because this immediately makes things harder (that is, effectively
9 impossible) to analyze.
10
11 16-bit `cmp` also benefits from some special differences between `cmp`
12 and `sub` on 6502, so it would be nice to capture them.
132
143 ### Save values to other-than-the-stack
154
5443 An alternative would be `static` pointers, which are currently not possible because
5544 pointers must be zero-page, thus `@`, thus uninitialized.
5645
57 ### Question "consistent initialization"
58
59 Question the value of the "consistent initialization" principle for `if` statement analysis.
60
61 Part of this is the trashes at the end; I think what it should be is that the trashes
62 after the `if` is the union of the trashes in each of the branches; this would obviate the
63 need to `trash` values explicitly, but if you tried to access them afterwards, it would still
64 error.
65
6646 ### Tail-call optimization
67
68 More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot
69 appear elsewhere.)
7047
7148 If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
7249 if the block is in tail position. The constraints should iron out the same both ways.
7350
74 And - once we have this - why do we need `goto` to be in tail position, strictly?
7551 As long as the routine has consistent type context every place it exits, that should be fine.
7652
7753 ### "Include" directives
287287
288288 copy <src-memory-location>, <dest-memory-location>
289289
290 Reads from src and writes to dest. Differs from `st` in that is able to
291 copy more general types of data (for example, vectors,) and it trashes the
292 `z` and `n` flags and the `a` register.
290 Reads from src and writes to dest. Differs from `ld` and `st` in that
291 it is able to copy more general types of data (for example, vectors,)
292 and it trashes the `z` and `n` flags and the `a` register.
293293
294294 * It is illegal if dest is read-only.
295295 * It is illegal if dest does not occur in the WRITES of the current routine.
332332
333333 Adds the contents of src to dest and stores the result in dest.
334334
335 * It is illegal if src OR dest OR c is uninitialized.
335 * It is illegal if src OR dest OR `c` is uninitialized.
336336 * It is illegal if dest is read-only.
337 * It is illegal if dest is `x` or `y`.
337338 * It is illegal if dest does not occur in the WRITES of the current routine.
338339
339340 Affects n, z, c, and v flags, requiring that they be in the WRITES,
344345 In addition, if dest is of `word` type, then src must also be of `word`
345346 type, and in this case this instruction trashes the `a` register.
346347
348 In fact, this instruction trashes the `a` register in all cases except
349 when the dest is `a`.
350
347351 NOTE: If dest is a pointer, the addition does not check if the result of
348352 the pointer arithmetic continues to be valid (within a buffer) or not.
349353
366370
367371 Subtracts the contents of src from dest and stores the result in dest.
368372
369 * It is illegal if src OR dest OR c is uninitialized.
373 * It is illegal if src OR dest OR `c` is uninitialized.
370374 * It is illegal if dest is read-only.
375 * It is illegal if dest is `x` or `y`.
371376 * It is illegal if dest does not occur in the WRITES of the current routine.
372377
373378 Affects n, z, c, and v flags, requiring that they be in the WRITES,
375380
376381 dest and src continue to be initialized afterwards.
377382
383 In addition, if dest is of `word` type, then src must also be of `word`
384 type, and in this case this instruction trashes the `a` register.
385
386 In fact, this instruction trashes the `a` register in all cases except
387 when the dest is `a`.
388
378389 ### dec ###
379390
380391 dec <dest-memory-location>
394405
395406 Subtracts the contents of src from dest (without considering carry) but
396407 does not store the result anywhere, only sets the resulting flags.
408 This means that `z` is set if src and dest are equal,
409 and `c` is set if dest is greater than or equal to src
410 (`c` is unset if dest is less than src.)
397411
398412 * It is illegal if src OR dest is uninitialized.
399413
400414 Affects n, z, and c flags, requiring that they be in the WRITES,
401415 and initializing them afterwards.
416
417 In addition, if dest is of `word` type, then src must also be of `word`
418 type, and in this case this instruction trashes the `a` register.
419
420 Note that `cmp` is not suitable for making a
421 signed comparison; this article, which mentions
422 techniques that a SixtyPical compiler could use to
423 implement `cmp`, also explains why that is:
424 [Beyond 8-bit Unsigned Comparisons, by Bruce Clark](http://www.6502.org/tutorials/compare_beyond.html).
402425
403426 ### and, or, xor ###
404427
310310 ld a, 1
311311 st a, player_died
312312 }
313
314 // FIXME these trashes, strictly speaking, probably shouldn't be needed,
315 // but currently the compiler cares a little too much about values that are
316 // initialized in one branch of an `if`, but not the other, but are trashed
317 // at the end of the routine anyway.
318 trash ptr
319 trash y
320 trash v
321313 }
322314 }
323315
354346 add ptr, pos
355347 copy 82, [ptr] + y
356348 }
357
358 // FIXME these trashes, strictly speaking, probably shouldn't be needed,
359 // but currently the compiler cares too much about values that are
360 // initialized in one branch of an `if`, but not the other, but trashed
361 // at the end of the routine anyway.
362 trash ptr
363 trash y
364349 } else {
365350 copy delta, compare_target
366351 st on, c
00 This directory contains example sources which demonstrate
11 the rudiments of SixtyPical.
22
3 Some are meant to fail and produce an error message.
3 Examples that are meant to fail and produce an error message
4 are in the `errorful/` subdirectory.
45
5 They are not meant to be specific to any architecture, but
6 many do assume the existence of a routine at 65490 which
7 outputs the value of the accumulator as an ASCII character,
6 These files are intended to be architecture-agnostic.
7 For the ones that do produce output, an appropriate source
8 under `platform/`, should be included first, like
9
10 sixtypical platform/c64.60p vector-table.60p
11
12 so that system entry points such as `chrout` are defined.
13
14 There's a `loadngo.sh` script in this directory that does this.
15
16 ./loadngo.sh c64 vector-table.60p
17
18 `chrout` is a routine with outputs the value of the accumulator
19 as an ASCII character, disturbing none of the other registers,
820 simply for the purposes of producing some observable output.
9 (This is an address of a KERNAL routine which does this
10 on both the Commodore 64 and the Commodore VIC-20, so these
11 sources should be usable on these architectures.)
21
22 (There is a KERNAL routine which does this on both the
23 Commodore 64 and the Commodore VIC-20. It should not be hard
24 to find or write such a routine for most other architectures.)
+0
-6
eg/rudiments/add-fail.60p less more
0 define add_four routine
1 inputs a
2 outputs a
3 {
4 add a, 4
5 }
+0
-8
eg/rudiments/add-pass.60p less more
0 define main routine
1 inputs a
2 outputs a
3 trashes c, z, n, v
4 {
5 st off, c
6 add a, 4
7 }
+0
-9
eg/rudiments/add-word.60p less more
0 word score
1 define main routine
2 inputs score
3 outputs score
4 trashes a, c, z, v, n
5 {
6 st off, c
7 add score, 1999
8 }
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print YY
2
3 word score
4
5 define main routine
6 inputs a, score
7 outputs score
8 trashes a, c, z, n, v
9 {
10 ld a, 3
11 st off, c
12 add a, 4
13
14 cmp a, 7
15 if z {
16 ld a, 89
17 call chrout
18 } else {
19 ld a, 78
20 call chrout
21 }
22
23 copy 999, score
24 st off, c
25 add score, 1999
26
27 cmp score, 2998
28 if z {
29 ld a, 89
30 call chrout
31 } else {
32 ld a, 78
33 call chrout
34 }
35 }
+0
-20
eg/rudiments/bad-vector.60p less more
0 vector vec
1 inputs y
2 outputs y
3 trashes z, n
4
5 routine foo
6 inputs x
7 outputs x
8 trashes z, n
9 {
10 inc x
11 }
12
13 routine main
14 inputs foo
15 outputs vec
16 trashes a, z, n
17 {
18 copy foo, vec
19 }
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print Y
2
03 buffer[2048] buf
14 pointer ptr @ 254
25 byte foo
47 define main routine
58 inputs buf
69 outputs buf, y, foo
7 trashes a, z, n, ptr
10 trashes a, z, n, c, ptr
811 {
912 ld y, 0
1013 copy ^buf, ptr
1114 copy 123, [ptr] + y
1215 copy [ptr] + y, foo
1316 copy foo, [ptr] + y
17
18 // TODO: support saying `cmp foo, 123`, maybe
19 ld a, foo
20 cmp a, 123
21
22 if z {
23 ld a, 89
24 call chrout
25 } else {
26 ld a, 78
27 call chrout
28 }
1429 }
0 define chrout routine
1 inputs a
2 trashes a
3 @ 65490
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print AA
42
53 define print routine
64 trashes a, z, n
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print ENGGL
2
3 byte b
4
5 define main routine
6 outputs b
7 trashes a, x, y, z, n, c, v
8 {
9 ld a, 40
10 st a, b
11
12 cmp a, b
13 if z {
14 ld a, 69 // E
15 call chrout
16 } else {
17 ld a, 78 // N
18 call chrout
19 }
20
21 ld a, 41
22 st a, b
23 ld a, 40
24
25 cmp a, b
26 if z {
27 ld a, 69 // E
28 call chrout
29 } else {
30 ld a, 78 // N
31 call chrout
32 }
33
34 ld a, 20
35 st a, b
36
37 ld a, 21
38
39 cmp a, b // 21 >= 20
40 if c {
41 ld a, 71 // G
42 call chrout
43 } else {
44 ld a, 76 // L
45 call chrout
46 }
47
48 ld a, 20
49
50 cmp a, b // 20 >= 20
51 if c {
52 ld a, 71 // G
53 call chrout
54 } else {
55 ld a, 76 // L
56 call chrout
57 }
58
59 ld a, 19
60
61 cmp a, b // 19 < 20
62 if c {
63 ld a, 71 // G
64 call chrout
65 } else {
66 ld a, 76 // L
67 call chrout
68 }
69 }
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print ENGGL
2
3 word w1
4
5 define main routine
6 outputs w1
7 trashes a, x, y, z, n, c, v
8 {
9 copy 4000, w1
10
11 cmp w1, 4000
12 if z {
13 ld a, 69 // E
14 call chrout
15 } else {
16 ld a, 78 // N
17 call chrout
18 }
19
20 copy 4000, w1
21
22 cmp w1, 4001
23 if z {
24 ld a, 69 // E
25 call chrout
26 } else {
27 ld a, 78 // N
28 call chrout
29 }
30
31 copy 20002, w1
32
33 cmp w1, 20001 // 20002 >= 20001
34 if c {
35 ld a, 71 // G
36 call chrout
37 } else {
38 ld a, 76 // L
39 call chrout
40 }
41
42 copy 20001, w1
43
44 cmp w1, 20001 // 20001 >= 20001
45 if c {
46 ld a, 71 // G
47 call chrout
48 } else {
49 ld a, 76 // L
50 call chrout
51 }
52
53 copy 20000, w1
54
55 cmp w1, 20001 // 20000 < 20001
56 if c {
57 ld a, 71 // G
58 call chrout
59 } else {
60 ld a, 76 // L
61 call chrout
62 }
63 }
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print ENGGL
2
3 word w1
4 word w2
5
6 define main routine
7 outputs w1, w2
8 trashes a, x, y, z, n, c, v
9 {
10 copy 4000, w1
11 copy 4000, w2
12
13 cmp w1, w2
14 if z {
15 ld a, 69 // E
16 call chrout
17 } else {
18 ld a, 78 // N
19 call chrout
20 }
21
22 copy 4000, w1
23 copy 4001, w2
24
25 cmp w1, w2
26 if z {
27 ld a, 69 // E
28 call chrout
29 } else {
30 ld a, 78 // N
31 call chrout
32 }
33
34 copy 20002, w1
35 copy 20001, w2
36
37 cmp w1, w2 // 20002 >= 20001
38 if c {
39 ld a, 71 // G
40 call chrout
41 } else {
42 ld a, 76 // L
43 call chrout
44 }
45
46 copy 20001, w1
47
48 cmp w1, w2 // 20001 >= 20001
49 if c {
50 ld a, 71 // G
51 call chrout
52 } else {
53 ld a, 76 // L
54 call chrout
55 }
56
57 copy 20000, w1
58
59 cmp w1, w2 // 20000 < 20001
60 if c {
61 ld a, 71 // G
62 call chrout
63 } else {
64 ld a, 76 // L
65 call chrout
66 }
67 }
0 define chrout routine
1 inputs a
2 trashes a
3 @ 65490
0 // Demonstrates vector tables.
1 // Include `support/${PLATFORM}.60p` before this source
2 // Should print YN
43
54 define main routine
65 trashes a, x, y, z, n, c, v
0 define chrout routine
1 inputs a
2 trashes a
3 @ 65490
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print YA
42
53 define main routine
64 trashes a, x, y, z, n, c, v
+0
-10
eg/rudiments/copy.60p less more
0 byte bar
1 byte baz
2
3 define main routine
4 inputs baz
5 outputs bar
6 trashes a, n, z
7 {
8 copy baz, bar
9 }
0 This directory contains example SixtyPical sources which
1 are intentionally invalid (for demonstration purposes)
2 and are expected to elicit an error message from a
3 SixtyPical implementation.
0 define add_four routine
1 inputs a
2 outputs a
3 {
4 add a, 4
5 }
0 byte table[8] message : "WHAT?"
1
2 define main routine
3 inputs message
4 outputs x, a, z, n
5 {
6 ld x, 9
7 ld a, message + x
8 }
0 vector vec
1 inputs y
2 outputs y
3 trashes z, n
4
5 routine foo
6 inputs x
7 outputs x
8 trashes z, n
9 {
10 inc x
11 }
12
13 routine main
14 inputs foo
15 outputs vec
16 trashes a, z, n
17 {
18 copy foo, vec
19 }
0 // Include `support/${PLATFORM}.60p` and `support/stdlib.60p` before this source
1 // Should print 01
2
03 byte lives
14
25 define main routine
3 inputs lives
6 inputs lives, hexchars
47 outputs lives
58 trashes a, x, z, n, c, v
6 {
7 ld a, 0
8 st a, lives
9 ld x, lives
10 st off, c
11 add x, 1
12 st x, lives
13 }
9 {
10 ld a, 0
11 st a, lives
12 ld x, lives
13 st off, c
14 inc x
15 st x, lives
16 ld a, lives
17 call prbyte
18 }
0 // This program is expected to loop forever.
1 // Be prepared to forcibly terminate your emulator.
2
03 define main routine
14 trashes a, y, z, n, c
25 {
0 define chrout routine
1 inputs a
2 trashes a
3 @ 65490
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print AB
42
53 define bar routine trashes a, z, n {
64 ld a, 66
+0
-12
eg/rudiments/if.60p less more
0 define main routine
1 inputs a
2 outputs a
3 trashes z, n, c
4 {
5 cmp a, 42
6 if z {
7 ld a, 7
8 } else {
9 ld a, 23
10 }
11 }
0 #!/bin/sh
1
2 usage="Usage: loadngo.sh (c64|vic20) <source.60p>"
3
4 arch="$1"
5 shift 1
6 if [ "X$arch" = "Xc64" ]; then
7 output_format='c64-basic-prg'
8 if [ -e vicerc ]; then
9 emu="x64 -config vicerc"
10 else
11 emu="x64"
12 fi
13 elif [ "X$arch" = "Xvic20" ]; then
14 output_format='vic20-basic-prg'
15 if [ -e vicerc ]; then
16 emu="xvic -config vicerc"
17 else
18 emu="xvic"
19 fi
20 else
21 echo $usage && exit 1
22 fi
23
24 src="$1"
25 if [ "X$src" = "X" ]; then
26 echo $usage && exit 1
27 fi
28
29 ### do it ###
30
31 out=/tmp/a-out.prg
32 ../../bin/sixtypical --traceback --output-format=$output_format support/$arch.60p support/stdlib.60p $src --output $out || exit 1
33 ls -la $out
34 $emu $out
35 rm -f $out
0 define chrout routine
1 inputs a
2 trashes a
3 @ 65490
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ
42
53 define main routine
64 trashes a, y, z, n, c
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print AB
2
03 byte foo
1
2 define chrout routine
3 inputs a
4 trashes a
5 @ 65490
64
75 define print routine
86 inputs foo
+0
-22
eg/rudiments/new-style-routine.60p less more
0 // SixtyPical 0.11 introduced a new syntax for defining routines
1 // (routine was made into a type, and you can now say: define name type.)
2
3 typedef routine
4 inputs x
5 outputs x
6 trashes z, n
7 routine_type
8
9 vector routine_type vec
10
11 define foo routine_type
12 {
13 inc x
14 }
15
16 define main routine
17 outputs vec
18 trashes a, z, n
19 {
20 copy foo, vec
21 }
0 define chrout routine
1 inputs a
2 trashes a
3 @ 65490
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print A
42
53 define main routine
64 inputs a
+0
-9
eg/rudiments/range-error.60p less more
0 byte table[8] message : "WHAT?"
1
2 define main routine
3 inputs message
4 outputs x, a, z, n
5 {
6 ld x, 9
7 ld a, message + x
8 }
0 // Implementation of `chrout` for the Commodore 64 platform.
1
2 define chrout routine
3 inputs a
4 trashes a
5 @ 65490
0 byte table[16] hexchars : "0123456789ABCDEF"
1
2 define prbyte routine
3 inputs a, hexchars
4 trashes a, z, n, c, v
5 {
6 save x {
7 save a {
8 st off, c
9 shr a
10 shr a
11 shr a
12 shr a
13 and a, 15
14 ld x, a
15 ld a, hexchars + x
16 call chrout
17 }
18 save a {
19 and a, 15
20 ld x, a
21 ld a, hexchars + x
22 call chrout
23 }
24 }
25 }
0 // Implementation of `chrout` for the Commodore VIC-20 platform.
1
2 define chrout routine
3 inputs a
4 trashes a
5 @ 65490
+0
-21
eg/rudiments/vector-inc.60p less more
0 // This will not compile on its own, because there is no `main`.
1 // But this and `vector-main.60p` together will compile.
2
3 define chrout routine
4 inputs a
5 trashes a
6 @ 65490
7
8 define printa routine
9 trashes a, z, n
10 {
11 ld a, 65
12 call chrout
13 }
14
15 define printb routine
16 trashes a, z, n
17 {
18 ld a, 66
19 call chrout
20 }
+0
-22
eg/rudiments/vector-main.60p less more
0 // This will not compile on its own, because `printa` and `printb` are not defined.
1 // But `vector-inc.60p` and this together will compile.
2
3 vector routine
4 trashes a, z, n
5 print
6
7 // routine printb
8 // trashes a, z, n
9 // {
10 // ld a, 66
11 // call chrout
12 // }
13
14 define main routine
15 trashes print, a, z, n
16 {
17 copy printa, print
18 call print
19 copy printb, print
20 call print
21 }
0 //
10 // Demonstrates vector tables.
2 // Prints "AABAB".
3 //
1 // Include `support/${PLATFORM}.60p` before this source
2 // Should print AABAB
43
54 vector routine
65 trashes a, z, n
98 vector (routine
109 trashes a, z, n)
1110 table[32] vectors
12
13 define chrout routine
14 inputs a
15 trashes a
16 @ 65490
1711
1812 define printa routine
1913 trashes a, z, n
4842 copy printb, vectors + x
4943
5044 ld x, 0
51 repeat {
45 for x up to 4 {
5246 copy vectors + x, print
5347 call print
54 inc x
55 cmp x, 5
56 } until z
48 }
5749 }
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print AB
2
03 vector routine
14 trashes a, z, n
25 print
3
4 define chrout routine
5 inputs a
6 trashes a
7 @ 65490
86
97 define printa routine
108 trashes a, z, n
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print YY
2
03 word one
14 word table[256] many
25
36 define main routine
47 inputs one, many
58 outputs one, many
6 trashes a, x, y, n, z
9 trashes a, x, y, c, n, z
710 {
811 ld x, 0
9 ld y, 0
12 ld y, 1
1013 copy 777, one
1114 copy one, many + x
15 copy 888, one
1216 copy one, many + y
17
18 ld x, 1
19 ld y, 0
20
1321 copy many + x, one
22 cmp one, 888
23 if z {
24 ld a, 89
25 call chrout
26 } else {
27 ld a, 78
28 call chrout
29 }
30
1431 copy many + y, one
32 cmp one, 777
33 if z {
34 ld a, 89
35 call chrout
36 } else {
37 ld a, 78
38 call chrout
39 }
1540 }
3333 pass
3434
3535
36 class InconsistentExitError(StaticAnalysisError):
37 """The type context differs at two different exit points of the routine."""
38 pass
39
40
3641 class ForbiddenWriteError(StaticAnalysisError):
3742 pass
3843
4247
4348
4449 class IllegalJumpError(StaticAnalysisError):
50 pass
51
52
53 class TerminatedContextError(StaticAnalysisError):
54 """What the program is doing here is not valid, due to preceding `goto`s,
55 which make this dead code."""
4556 pass
4657
4758
100111 self._touched = set()
101112 self._range = dict()
102113 self._writeable = set()
114 self._terminated = False
103115 self._gotos_encountered = set()
104116
105117 for ref in inputs:
131143 c._writeable = set(self._writeable)
132144 return c
133145
146 def update_from(self, other):
147 self.routines = other.routines
148 self.routine = other.routine
149 self._touched = set(other._touched)
150 self._range = dict(other._range)
151 self._writeable = set(other._writeable)
152 self._terminated = other._terminated
153 self._gotos_encounters = set(other._gotos_encountered)
154
134155 def each_meaningful(self):
135156 for ref in self._range.keys():
136157 yield ref
137158
138159 def each_touched(self):
139160 for ref in self._touched:
161 yield ref
162
163 def each_writeable(self):
164 for ref in self._writeable:
140165 yield ref
141166
142167 def assert_meaningful(self, *refs, **kwargs):
278303 def encountered_gotos(self):
279304 return self._gotos_encountered
280305
306 def set_terminated(self):
307 # Having a terminated context and having encountered gotos is not the same thing.
308 self._terminated = True
309
310 def has_terminated(self):
311 return self._terminated
312
281313 def assert_types_for_read_table(self, instr, src, dest, type_):
282314 if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_):
283315 raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name))
363395
364396 def analyze_routine(self, routine):
365397 assert isinstance(routine, Routine)
366 self.current_routine = routine
367398 if routine.block is None:
368399 # it's an extern, that's fine
369400 return
401
402 self.current_routine = routine
370403 type_ = routine.location.type
371404 context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes)
405 self.exit_contexts = []
372406
373407 if self.debug:
374408 print("at start of routine `{}`:".format(routine.name))
375409 print(context)
376410
377411 self.analyze_block(routine.block, context)
412
378413 trashed = set(context.each_touched()) - set(context.each_meaningful())
379414
380415 if self.debug:
389424 print('-' * 79)
390425 print('')
391426
392 # even if we goto another routine, we can't trash an output.
427 if self.exit_contexts:
428 # check that they are all consistent
429 exit_context = self.exit_contexts[0]
430 exit_meaningful = set(exit_context.each_meaningful())
431 exit_touched = set(exit_context.each_touched())
432 exit_writeable = set(exit_context.each_writeable())
433 for ex in self.exit_contexts[1:]:
434 if set(ex.each_meaningful()) != exit_meaningful:
435 raise InconsistentExitError("Exit contexts are not consistent")
436 if set(ex.each_touched()) != exit_touched:
437 raise InconsistentExitError("Exit contexts are not consistent")
438 if set(ex.each_writeable()) != exit_writeable:
439 raise InconsistentExitError("Exit contexts are not consistent")
440 context.update_from(exit_context)
441
442 # these all apply whether we encountered goto(s) in this routine, or not...:
443
444 # can't trash an output.
393445 for ref in trashed:
394446 if ref in type_.outputs:
395447 raise UnmeaningfulOutputError(routine, ref.name)
396448
397 if not context.encountered_gotos():
398 for ref in type_.outputs:
399 context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
400 for ref in context.each_touched():
401 if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref):
402 raise ForbiddenWriteError(routine, ref.name)
449 # all outputs are meaningful.
450 for ref in type_.outputs:
451 context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
452
453 # if something was touched, then it should have been declared to be writable.
454 for ref in context.each_touched():
455 if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref):
456 raise ForbiddenWriteError(routine, ref.name)
457
458 self.exit_contexts = None
403459 self.current_routine = None
404460 return context
405461
432488 dest = instr.dest
433489 src = instr.src
434490
435 if context.encountered_gotos():
436 raise IllegalJumpError(instr, instr)
491 if context.has_terminated():
492 raise TerminatedContextError(instr, instr)
437493
438494 if opcode == 'ld':
439495 if isinstance(src, IndexedRef):
476532 context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
477533 elif src.type == TYPE_BYTE:
478534 self.assert_type(TYPE_BYTE, src, dest)
535 if dest != REG_A:
536 context.set_touched(REG_A)
537 context.set_unmeaningful(REG_A)
479538 else:
480539 self.assert_type(TYPE_WORD, src)
481540 if dest.type == TYPE_WORD:
494553 context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
495554 elif src.type == TYPE_BYTE:
496555 self.assert_type(TYPE_BYTE, src, dest)
556 if dest != REG_A:
557 context.set_touched(REG_A)
558 context.set_unmeaningful(REG_A)
497559 else:
498560 self.assert_type(TYPE_WORD, src, dest)
499561 context.set_touched(REG_A)
504566 context.assert_meaningful(src, dest)
505567 if isinstance(src, IndexedRef):
506568 context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
507 else:
569 elif src.type == TYPE_BYTE:
508570 self.assert_type(TYPE_BYTE, src, dest)
571 else:
572 self.assert_type(TYPE_WORD, src, dest)
573 context.set_touched(REG_A)
574 context.set_unmeaningful(REG_A)
509575 context.set_written(FLAG_Z, FLAG_N, FLAG_C)
510576 elif opcode == 'and':
511577 if isinstance(src, IndexedRef):
536602 else:
537603 self.assert_type(TYPE_BYTE, dest)
538604 context.set_written(dest, FLAG_Z, FLAG_N)
539 context.invalidate_range(dest)
605 bottom = context.get_bottom_of_range(dest)
606 top = context.get_top_of_range(dest)
607 if opcode == 'inc':
608 if bottom == top and top < 255:
609 context.set_range(dest, bottom + 1, top + 1)
610 else:
611 context.invalidate_range(dest)
612 elif opcode == 'dec':
613 if bottom == top and bottom > 0:
614 context.set_range(dest, bottom - 1, top - 1)
615 else:
616 context.invalidate_range(dest)
617 else:
618 raise NotImplementedError
540619 elif opcode in ('shl', 'shr'):
541620 context.assert_meaningful(dest, FLAG_C)
542621 if isinstance(dest, IndexedRef):
673752 self.assert_affected_within('trashes', type_, current_type)
674753
675754 context.encounter_gotos(set([instr.location]))
755
756 # Now that we have encountered a goto, we update the
757 # context here to match what someone calling the goto'ed
758 # function directly, would expect. (which makes sense
759 # when you think about it; if this goto's F, then calling
760 # this is like calling F, from the perspective of what is
761 # returned.)
762 #
763 # However, this isn't the current context anymore. This
764 # is an exit context of this routine.
765
766 exit_context = context.clone()
767
768 for ref in type_.outputs:
769 exit_context.set_touched(ref) # ?
770 exit_context.set_written(ref)
771
772 for ref in type_.trashes:
773 exit_context.assert_writeable(ref)
774 exit_context.set_touched(ref)
775 exit_context.set_unmeaningful(ref)
776
777 self.exit_contexts.append(exit_context)
778
779 # When we get to the end, we'll check that all the
780 # exit contexts are consistent with each other.
781
782 # We set the current context as having terminated.
783 # If we are in a branch, the merge will deal with
784 # having terminated. If we are at the end of the
785 # routine, the routine end will deal with that.
786
787 context.set_terminated()
788
676789 elif opcode == 'trash':
677790 context.set_touched(instr.dest)
678791 context.set_unmeaningful(instr.dest)
693806 outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful())
694807 outgoing_trashes = incoming_meaningful - outgoing_meaningful
695808
696 # TODO may we need to deal with touched separately here too?
697 # probably not; if it wasn't meaningful in the first place, it
698 # doesn't really matter if you modified it or not, coming out.
699 for ref in context1.each_meaningful():
700 if ref in outgoing_trashes:
701 continue
702 context2.assert_meaningful(
703 ref, exception_class=InconsistentInitializationError,
704 message='initialized in block 1 but not in block 2 of `if {}`'.format(instr.src)
705 )
706 for ref in context2.each_meaningful():
707 if ref in outgoing_trashes:
708 continue
709 context1.assert_meaningful(
710 ref, exception_class=InconsistentInitializationError,
711 message='initialized in block 2 but not in block 1 of `if {}`'.format(instr.src)
712 )
713
714 # merge the contexts. this used to be a method called `set_from`
715 context._touched = set(context1._touched) | set(context2._touched)
716 context.set_meaningful(*list(outgoing_meaningful))
717 context._writeable = set(context1._writeable) | set(context2._writeable)
809 # merge the contexts.
810
811 # first, the easy case: if one of the contexts has terminated, just use the other one.
812 # if both have terminated, we return a terminated context, and that's OK.
813
814 if context1.has_terminated():
815 context.update_from(context2)
816 elif context2.has_terminated():
817 context.update_from(context1)
818 else:
819 # the more complicated case: merge the contents of the contexts.
820 context._touched = set(context1._touched) | set(context2._touched)
821 context.set_meaningful(*list(outgoing_meaningful))
822 context._writeable = set(context1._writeable) | set(context2._writeable)
823
824 # in both cases, we need to merge the encountered gotos, in order that
825 # fallthru optimization continues to work correctly.
718826 context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos())
719827
720828 for ref in outgoing_trashes:
727835 self.analyze_block(instr.block, context)
728836 if instr.src is not None: # None indicates 'repeat forever'
729837 context.assert_meaningful(instr.src)
838
839 if context.encountered_gotos():
840 raise IllegalJumpError(instr, instr)
730841
731842 # now analyze it having been executed a second time, with the context
732843 # of it having already been executed.
243243 raise UnsupportedOpcodeError(instr)
244244 self.emitter.emit(op_cls(operand))
245245 elif opcode == 'add':
246 if dest == REG_X or dest == REG_Y:
247 raise UnsupportedOpcodeError(instr)
246248 if dest == REG_A:
247249 if isinstance(src, ConstantRef):
248250 self.emitter.emit(ADC(Immediate(Byte(src.value))))
250252 self.emitter.emit(ADC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
251253 else:
252254 self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
255 elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE:
256 if isinstance(src, ConstantRef):
257 dest_label = self.get_label(dest.name)
258 self.emitter.emit(LDA(Absolute(dest_label)))
259 self.emitter.emit(ADC(Immediate(Byte(src.low_byte()))))
260 self.emitter.emit(STA(Absolute(dest_label)))
261 elif isinstance(src, LocationRef):
262 src_label = self.get_label(src.name)
263 dest_label = self.get_label(dest.name)
264 self.emitter.emit(LDA(Absolute(dest_label)))
265 self.emitter.emit(ADC(Absolute(src_label)))
266 self.emitter.emit(STA(Absolute(dest_label)))
267 else:
268 raise UnsupportedOpcodeError(instr)
253269 elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
254270 if isinstance(src, ConstantRef):
255271 dest_label = self.get_label(dest.name)
293309 else:
294310 raise UnsupportedOpcodeError(instr)
295311 elif opcode == 'sub':
312 if dest == REG_X or dest == REG_Y:
313 raise UnsupportedOpcodeError(instr)
296314 if dest == REG_A:
297315 if isinstance(src, ConstantRef):
298316 self.emitter.emit(SBC(Immediate(Byte(src.value))))
300318 self.emitter.emit(SBC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
301319 else:
302320 self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
321 elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE:
322 if isinstance(src, ConstantRef):
323 dest_label = self.get_label(dest.name)
324 self.emitter.emit(LDA(Absolute(dest_label)))
325 self.emitter.emit(SBC(Immediate(Byte(src.low_byte()))))
326 self.emitter.emit(STA(Absolute(dest_label)))
327 elif isinstance(src, LocationRef):
328 src_label = self.get_label(src.name)
329 dest_label = self.get_label(dest.name)
330 self.emitter.emit(LDA(Absolute(dest_label)))
331 self.emitter.emit(SBC(Absolute(src_label)))
332 self.emitter.emit(STA(Absolute(dest_label)))
333 else:
334 raise UnsupportedOpcodeError(instr)
303335 elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
304336 if isinstance(src, ConstantRef):
305337 dest_label = self.get_label(dest.name)
390422
391423 def compile_cmp(self, instr, src, dest):
392424 """`instr` is only for reporting purposes"""
425 if isinstance(src, LocationRef) and src.type == TYPE_WORD:
426 src_label = self.get_label(src.name)
427 dest_label = self.get_label(dest.name)
428 self.emitter.emit(LDA(Absolute(dest_label)))
429 self.emitter.emit(CMP(Absolute(src_label)))
430 end_label = Label('end_label')
431 self.emitter.emit(BNE(Relative(end_label)))
432 self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
433 self.emitter.emit(CMP(Absolute(Offset(src_label, 1))))
434 self.emitter.resolve_label(end_label)
435 return
436 if isinstance(src, ConstantRef) and src.type == TYPE_WORD:
437 dest_label = self.get_label(dest.name)
438 self.emitter.emit(LDA(Absolute(dest_label)))
439 self.emitter.emit(CMP(Immediate(Byte(src.low_byte()))))
440 end_label = Label('end_label')
441 self.emitter.emit(BNE(Relative(end_label)))
442 self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
443 self.emitter.emit(CMP(Immediate(Byte(src.high_byte()))))
444 self.emitter.resolve_label(end_label)
445 return
393446 cls = {
394447 'a': CMP,
395448 'x': CPX,
379379 self.scanner.expect('{')
380380 while not self.scanner.on('}'):
381381 instrs.append(self.instr())
382 if isinstance(instrs[-1], SingleOp) and instrs[-1].opcode == 'goto':
383 break
382384 self.scanner.expect('}')
383385 return Block(self.scanner.line_number, instrs=instrs)
384386
1616 self.filename = filename
1717 self.token = None
1818 self.type = None
19 self.pos = 0
1920 self.line_number = 1
2021 self.scan()
2122
22 def scan_pattern(self, pattern, type, token_group=1, rest_group=2):
23 pattern = r'^(' + pattern + r')(.*?)$'
24 match = re.match(pattern, self.text, re.DOTALL)
23 def scan_pattern(self, pattern, type, token_group=1):
24 pattern = r'(' + pattern + r')'
25 regexp = re.compile(pattern, flags=re.DOTALL)
26 match = regexp.match(self.text, pos=self.pos)
2527 if not match:
2628 return False
2729 else:
2830 self.type = type
2931 self.token = match.group(token_group)
30 self.text = match.group(rest_group)
32 self.pos += len(match.group(0))
3133 self.line_number += self.token.count('\n')
3234 return True
3335
3537 self.scan_pattern(r'[ \t\n\r]*', 'whitespace')
3638 while self.scan_pattern(r'\/\/.*?[\n\r]', 'comment'):
3739 self.scan_pattern(r'[ \t\n\r]*', 'whitespace')
38 if not self.text:
40 if self.pos >= len(self.text):
3941 self.token = None
4042 self.type = 'EOF'
4143 return
4345 return
4446 if self.scan_pattern(r'\d+', 'integer literal'):
4547 return
46 if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal',
47 token_group=2, rest_group=3):
48 if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal', token_group=2):
4849 # ecch
4950 self.token = str(eval('0x' + self.token))
5051 return
51 if self.scan_pattern(r'\"(.*?)\"', 'string literal',
52 token_group=2, rest_group=3):
52 if self.scan_pattern(r'\"(.*?)\"', 'string literal', token_group=2):
5353 return
5454 if self.scan_pattern(r'\w+', 'identifier'):
5555 return
5656 if self.scan_pattern(r'.', 'unknown character'):
5757 return
5858 else:
59 raise AssertionError("this should never happen, self.text=({})".format(self.text))
59 raise AssertionError("this should never happen, self.text=({}), self.pos=({})".format(self.text, self.pos))
6060
6161 def expect(self, token):
6262 if self.token == token:
761761 | }
762762 ? RangeExceededError
763763
764 When the range of a location is known, incrementing or
765 decrementing that location's value will shift the known
766 range. It will not invalidate it unless the known range
767 is at the limits of the possible ranges for the type.
768
769 | vector routine
770 | trashes a, z, n
771 | print
772 |
773 | vector (routine
774 | trashes a, z, n)
775 | table[32] vectors
776 |
777 | define main routine
778 | inputs vectors, print
779 | outputs vectors
780 | trashes print, a, x, z, n, c
781 | {
782 | ld x, 0
783 | inc x
784 | copy print, vectors + x
785 | }
786 = ok
787
788 | vector routine
789 | trashes a, z, n
790 | print
791 |
792 | vector (routine
793 | trashes a, z, n)
794 | table[32] vectors
795 |
796 | define main routine
797 | inputs vectors, print
798 | outputs vectors
799 | trashes print, a, x, z, n, c
800 | {
801 | ld x, 32
802 | dec x
803 | copy print, vectors + x
804 | }
805 = ok
806
764807 ### add ###
765808
766809 Can't `add` from or to a memory location that isn't initialized.
808851 | }
809852 ? ForbiddenWriteError: a
810853
854 You can `add` a byte constant to a byte memory location.
855
856 | byte lives
857 | define main routine
858 | inputs a, lives
859 | outputs lives
860 | trashes a, c, z, v, n
861 | {
862 | st off, c
863 | add lives, 3
864 | }
865 = ok
866
867 `add`ing a byte constant to a byte memory location trashes `a`.
868
869 | byte lives
870 | define main routine
871 | inputs a, lives
872 | outputs a, lives
873 | trashes c, z, v, n
874 | {
875 | st off, c
876 | add lives, 3
877 | }
878 ? UnmeaningfulOutputError: a
879
880 You can `add` a byte memory location to another byte memory location.
881 This trashes `a`.
882
883 | byte lives
884 | byte extra
885 | define main routine
886 | inputs a, lives, extra
887 | outputs lives
888 | trashes a, c, z, v, n
889 | {
890 | st off, c
891 | add lives, extra
892 | }
893 = ok
894
895 | byte lives
896 | byte extra
897 | define main routine
898 | inputs a, lives, extra
899 | outputs a, lives
900 | trashes c, z, v, n
901 | {
902 | st off, c
903 | add lives, extra
904 | }
905 ? UnmeaningfulOutputError: a
906
811907 You can `add` a word constant to a word memory location.
812908
813909 | word score
9521048 | }
9531049 ? ForbiddenWriteError: a
9541050
1051 You can `sub` a byte constant from a byte memory location.
1052
1053 | byte lives
1054 | define main routine
1055 | inputs a, lives
1056 | outputs lives
1057 | trashes a, c, z, v, n
1058 | {
1059 | st on, c
1060 | sub lives, 3
1061 | }
1062 = ok
1063
1064 `sub`ing a byte constant from a byte memory location trashes `a`.
1065
1066 | byte lives
1067 | define main routine
1068 | inputs a, lives
1069 | outputs a, lives
1070 | trashes c, z, v, n
1071 | {
1072 | st on, c
1073 | sub lives, 3
1074 | }
1075 ? UnmeaningfulOutputError: a
1076
1077 You can `sub` a byte memory location from another byte memory location.
1078 This trashes `a`.
1079
1080 | byte lives
1081 | byte extra
1082 | define main routine
1083 | inputs a, lives, extra
1084 | outputs lives
1085 | trashes a, c, z, v, n
1086 | {
1087 | st on, c
1088 | sub lives, extra
1089 | }
1090 = ok
1091
1092 | byte lives
1093 | byte extra
1094 | define main routine
1095 | inputs a, lives, extra
1096 | outputs a, lives
1097 | trashes c, z, v, n
1098 | {
1099 | st on, c
1100 | sub lives, extra
1101 | }
1102 ? UnmeaningfulOutputError: a
1103
9551104 You can `sub` a word constant from a word memory location.
9561105
9571106 | word score
11161265 | cmp a, 4
11171266 | }
11181267 ? UnmeaningfulReadError: a
1268
1269 `cmp` can work on words. In this case, it trashes `a`.
1270
1271 | word za
1272 | word zb
1273 |
1274 | define main routine
1275 | inputs za, zb
1276 | trashes a, z, c, n
1277 | {
1278 | cmp za, zb
1279 | }
1280 = ok
1281
1282 | word za
1283 | word zb
1284 |
1285 | define main routine
1286 | inputs za, zb
1287 | trashes a, z, n
1288 | {
1289 | cmp za, zb
1290 | }
1291 ? ForbiddenWriteError: c
1292
1293 | word za
1294 | word zb
1295 |
1296 | define main routine
1297 | inputs za, zb
1298 | trashes z, c, n
1299 | {
1300 | cmp za, zb
1301 | }
1302 ? ForbiddenWriteError: a
1303
1304 | word za
1305 | word zb
1306 |
1307 | define main routine
1308 | inputs za
1309 | trashes z, c, n
1310 | {
1311 | cmp za, zb
1312 | }
1313 ? UnmeaningfulReadError: zb
1314
1315 `cmp` can compare against a literal word.
1316
1317 | word za
1318 |
1319 | define main routine
1320 | inputs za
1321 | trashes a, z, c, n
1322 | {
1323 | cmp za, 4000
1324 | }
1325 = ok
1326
1327 | word za
1328 |
1329 | define main routine
1330 | inputs za
1331 | trashes z, c, n
1332 | {
1333 | cmp za, 4000
1334 | }
1335 ? ForbiddenWriteError: a
11191336
11201337 ### and ###
11211338
15171734 | }
15181735 = ok
15191736
1520 If a location is initialized in one block, is must be initialized in the other as well.
1737 If a location is initialized in one block, it must be initialized in the other as well
1738 in order to be considered to be initialized after the block. If it is not consistent,
1739 it will be considered uninitialized.
15211740
15221741 | define foo routine
15231742 | inputs a
15311750 | ld a, 23
15321751 | }
15331752 | }
1534 ? InconsistentInitializationError: x
1753 ? UnmeaningfulOutputError: x
15351754
15361755 | define foo routine
15371756 | inputs a
15451764 | ld x, 7
15461765 | }
15471766 | }
1548 ? InconsistentInitializationError: x
1767 ? UnmeaningfulOutputError: x
15491768
15501769 | define foo routine
15511770 | inputs a
15591778 | ld x, 7
15601779 | }
15611780 | }
1562 ? InconsistentInitializationError: x
1781 ? UnmeaningfulOutputError: x
1782
1783 | define foo routine
1784 | inputs a
1785 | trashes a, x, z, n, c
1786 | {
1787 | cmp a, 42
1788 | if not z {
1789 | ld a, 6
1790 | } else {
1791 | ld x, 7
1792 | }
1793 | ld a, x
1794 | }
1795 ? UnmeaningfulReadError: x
1796
1797 If we don't care if it's uninitialized after the `if`, that's okay then.
1798
1799 | define foo routine
1800 | inputs a
1801 | trashes a, x, z, n, c
1802 | {
1803 | cmp a, 42
1804 | if not z {
1805 | ld a, 6
1806 | } else {
1807 | ld x, 7
1808 | }
1809 | }
1810 = ok
1811
1812 Or, if it does get initialized on both branches, that's okay then.
1813
1814 | define foo routine
1815 | inputs a
1816 | outputs x
1817 | trashes a, z, n, c
1818 | {
1819 | cmp a, 42
1820 | if not z {
1821 | ld x, 0
1822 | ld a, 6
1823 | } else {
1824 | ld x, 7
1825 | }
1826 | }
1827 = ok
15631828
15641829 However, this only pertains to initialization. If a value is already
15651830 initialized, either because it was set previous to the `if`, or is an
16081873 | ld x, 7
16091874 | }
16101875 | }
1611 ? InconsistentInitializationError: x
1876 ? UnmeaningfulOutputError: x
16121877
16131878 | define foo routine
16141879 | inputs a
22792544 | }
22802545 ? TypeMismatchError
22812546
2282 A `goto` cannot appear within a `save` block, even if it is otherwise in tail position.
2547 A `goto` cannot appear within a `save` block.
22832548
22842549 | define other routine
22852550 | trashes a, z, n
23242589 | }
23252590 = ok
23262591
2327 A `goto` cannot appear within a `with interrupts` block, even if it is
2328 otherwise in tail position.
2592 A `goto` cannot appear within a `with interrupts` block.
23292593
23302594 | vector routine
23312595 | inputs x
29723236 | }
29733237 ? UnmeaningfulOutputError: x
29743238
2975 `goto`, if present, must be in tail position (the final instruction in a routine.)
3239 For now at least, you cannot have a `goto` inside a `repeat` loop.
29763240
29773241 | define bar routine trashes x, z, n {
29783242 | ld x, 200
29803244 |
29813245 | define main routine trashes x, z, n {
29823246 | ld x, 0
2983 | goto bar
2984 | }
2985 = ok
3247 | repeat {
3248 | inc x
3249 | goto bar
3250 | } until z
3251 | }
3252 ? IllegalJumpError
3253
3254 `goto`, as a matter of syntax, can only appear at the end
3255 of a block; but it need not be the final instruction in a
3256 routine.
29863257
29873258 | define bar routine trashes x, z, n {
29883259 | ld x, 200
29893260 | }
29903261 |
29913262 | define main routine trashes x, z, n {
3263 | ld x, 0
29923264 | goto bar
2993 | ld x, 0
2994 | }
2995 ? IllegalJumpError
3265 | }
3266 = ok
29963267
29973268 | define bar routine trashes x, z, n {
29983269 | ld x, 200
30173288 | ld x, 1
30183289 | goto bar
30193290 | }
3020 | ld x, 0
3021 | }
3022 ? IllegalJumpError
3291 | goto bar
3292 | }
3293 = ok
3294
3295 | define bar routine trashes x, z, n {
3296 | ld x, 200
3297 | }
3298 |
3299 | define main routine trashes x, z, n {
3300 | ld x, 0
3301 | if z {
3302 | ld x, 1
3303 | goto bar
3304 | }
3305 | ld x, 0
3306 | }
3307 = ok
30233308
30243309 | define bar routine trashes x, z, n {
30253310 | ld x, 200
30523337 | }
30533338 = ok
30543339
3055 For the purposes of `goto`, the end of a loop is never tail position.
3056
30573340 | define bar routine trashes x, z, n {
30583341 | ld x, 200
30593342 | }
30603343 |
30613344 | define main routine trashes x, z, n {
30623345 | ld x, 0
3063 | repeat {
3064 | inc x
3346 | if z {
3347 | ld x, 1
30653348 | goto bar
3066 | } until z
3067 | }
3068 ? IllegalJumpError
3349 | } else {
3350 | ld x, 0
3351 | }
3352 | ld x, 0
3353 | }
3354 = ok
3355
3356 | define bar routine trashes x, z, n {
3357 | ld x, 200
3358 | }
3359 |
3360 | define main routine trashes x, z, n {
3361 | ld x, 0
3362 | if z {
3363 | ld x, 1
3364 | goto bar
3365 | } else {
3366 | ld x, 0
3367 | }
3368 | goto bar
3369 | }
3370 = ok
3371
3372 Even though `goto` can only appear at the end of a block,
3373 you can still wind up with dead code; the analysis detects
3374 this.
3375
3376 | define bar routine trashes x, z, n {
3377 | ld x, 200
3378 | }
3379 |
3380 | define main routine trashes x, z, n {
3381 | ld x, 0
3382 | if z {
3383 | ld x, 1
3384 | goto bar
3385 | } else {
3386 | ld x, 0
3387 | goto bar
3388 | }
3389 | ld x, 100
3390 | }
3391 ? TerminatedContextError
3392
3393 It is important that the type context at every
3394 `goto` is compatible with the type context at the end of
3395 the routine.
3396
3397 | define bar routine
3398 | inputs x
3399 | trashes x, z, n
3400 | {
3401 | ld x, 200
3402 | }
3403 |
3404 | define main routine trashes x, z, n {
3405 | ld x, 0
3406 | if z {
3407 | ld x, 1
3408 | goto bar
3409 | } else {
3410 | ld x, 0
3411 | }
3412 | ld x, 1
3413 | }
3414 = ok
3415
3416 Here, we try to trash `x` before `goto`ing a routine that inputs `x`.
3417
3418 | define bar routine
3419 | inputs x
3420 | trashes x, z, n
3421 | {
3422 | ld x, 200
3423 | }
3424 |
3425 | define main routine
3426 | outputs a
3427 | trashes x, z, n
3428 | {
3429 | ld x, 0
3430 | if z {
3431 | trash x
3432 | goto bar
3433 | } else {
3434 | trash x
3435 | }
3436 | ld a, 1
3437 | }
3438 ? UnmeaningfulReadError: x
3439
3440 Here, we declare that main outputs `a`, but we `goto` a routine that does not output `a`.
3441
3442 | define bar routine
3443 | inputs x
3444 | trashes x, z, n
3445 | {
3446 | ld x, 200
3447 | }
3448 |
3449 | define main routine
3450 | outputs a
3451 | trashes x, z, n
3452 | {
3453 | ld x, 0
3454 | if z {
3455 | ld x, 1
3456 | goto bar
3457 | } else {
3458 | ld x, 2
3459 | }
3460 | ld a, 1
3461 | }
3462 ? UnmeaningfulOutputError: a
3463
3464 Here, we declare that main outputs a, and we goto a routine that outputs a so that's OK.
3465
3466 | define bar routine
3467 | inputs x
3468 | outputs a
3469 | trashes x, z, n
3470 | {
3471 | ld x, 200
3472 | ld a, 1
3473 | }
3474 |
3475 | define main routine
3476 | outputs a
3477 | trashes x, z, n
3478 | {
3479 | ld x, 0
3480 | if z {
3481 | ld x, 1
3482 | goto bar
3483 | } else {
3484 | ld x, 2
3485 | }
3486 | ld a, 1
3487 | }
3488 = ok
3489
3490 Here, we declare that main outputs `a`, and we `goto` two routines, and they both output `a`.
3491
3492 | define bar0 routine
3493 | inputs x
3494 | outputs a
3495 | trashes x, z, n
3496 | {
3497 | ld a, x
3498 | }
3499 |
3500 | define bar1 routine
3501 | inputs x
3502 | outputs a
3503 | trashes x, z, n
3504 | {
3505 | ld a, 200
3506 | }
3507 |
3508 | define main routine
3509 | outputs a
3510 | trashes x, z, n
3511 | {
3512 | ld x, 0
3513 | if z {
3514 | ld x, 1
3515 | goto bar0
3516 | } else {
3517 | ld x, 2
3518 | goto bar1
3519 | }
3520 | }
3521 = ok
3522
3523 Here is like just above, but one routine doesn't output `a`.
3524
3525 | define bar0 routine
3526 | inputs x
3527 | outputs a
3528 | trashes x, z, n
3529 | {
3530 | ld a, x
3531 | }
3532 |
3533 | define bar1 routine
3534 | inputs x
3535 | trashes x, z, n
3536 | {
3537 | ld x, 200
3538 | }
3539 |
3540 | define main routine
3541 | outputs a
3542 | trashes x, z, n
3543 | {
3544 | ld x, 0
3545 | if z {
3546 | ld x, 1
3547 | goto bar0
3548 | } else {
3549 | ld x, 2
3550 | goto bar1
3551 | }
3552 | }
3553 ? InconsistentExitError
3554
3555 Here is like the above, but the two routines have different inputs, and that's OK.
3556
3557 | define bar0 routine
3558 | inputs x
3559 | outputs a
3560 | trashes x, z, n
3561 | {
3562 | ld a, x
3563 | }
3564 |
3565 | define bar1 routine
3566 | outputs a
3567 | trashes x, z, n
3568 | {
3569 | ld a, 200
3570 | }
3571 |
3572 | define main routine
3573 | outputs a
3574 | trashes x, z, n
3575 | {
3576 | ld x, 0
3577 | if z {
3578 | ld x, 1
3579 | goto bar0
3580 | } else {
3581 | ld x, 2
3582 | goto bar1
3583 | }
3584 | }
3585 = ok
3586
3587 TODO: we should have a lot more test cases for the above, here.
30693588
30703589 Can't `goto` a routine that outputs or trashes more than the current routine.
30713590
384384 = $081B DEC $081F,X
385385 = $081E RTS
386386
387 Compiling 16-bit `cmp`.
388
389 | word za @ 60001
390 | word zb : 3003
391 |
392 | define main routine
393 | inputs za, zb
394 | trashes a, z, c, n
395 | {
396 | cmp za, zb
397 | cmp za, 4000
398 | }
399 = $080D LDA $EA61
400 = $0810 CMP $0828
401 = $0813 BNE $081B
402 = $0815 LDA $EA62
403 = $0818 CMP $0829
404 = $081B LDA $EA61
405 = $081E CMP #$A0
406 = $0820 BNE $0827
407 = $0822 LDA $EA62
408 = $0825 CMP #$0F
409 = $0827 RTS
410 = $0828 .byte $BB
411 = $0829 .byte $0B
412
387413 Compiling `if`.
388414
389415 | define main routine
9971023 = $0841 RTS
9981024 = $0842 JMP ($0846)
9991025 = $0845 RTS
1026
1027 ### add, sub
1028
1029 Various modes of `add`.
1030
1031 | byte lives
1032 | byte extra
1033 | word score
1034 | word bonus
1035 | define main routine
1036 | inputs lives, score, extra, bonus
1037 | outputs lives, score
1038 | trashes a, x, y, c, z, v, n
1039 | {
1040 | ld a, 0
1041 | ld x, 0
1042 | ld y, 0
1043 | st off, c
1044 | add a, 7
1045 | add a, lives
1046 | add lives, 2
1047 | add lives, extra
1048 | add score, 1999
1049 | add score, bonus
1050 | }
1051 = $080D LDA #$00
1052 = $080F LDX #$00
1053 = $0811 LDY #$00
1054 = $0813 CLC
1055 = $0814 ADC #$07
1056 = $0816 ADC $084D
1057 = $0819 LDA $084D
1058 = $081C ADC #$02
1059 = $081E STA $084D
1060 = $0821 LDA $084D
1061 = $0824 ADC $084E
1062 = $0827 STA $084D
1063 = $082A LDA $084F
1064 = $082D ADC #$CF
1065 = $082F STA $084F
1066 = $0832 LDA $0850
1067 = $0835 ADC #$07
1068 = $0837 STA $0850
1069 = $083A LDA $084F
1070 = $083D ADC $0851
1071 = $0840 STA $084F
1072 = $0843 LDA $0850
1073 = $0846 ADC $0852
1074 = $0849 STA $0850
1075 = $084C RTS
1076
1077 Various modes of `sub`.
1078
1079 | byte lives
1080 | byte extra
1081 | word score
1082 | word bonus
1083 | define main routine
1084 | inputs lives, score, extra, bonus
1085 | outputs lives, score
1086 | trashes a, x, y, c, z, v, n
1087 | {
1088 | ld a, 0
1089 | ld x, 0
1090 | ld y, 0
1091 | st on, c
1092 | sub a, 7
1093 | sub a, lives
1094 | sub lives, 2
1095 | sub lives, extra
1096 | sub score, 1999
1097 | sub score, bonus
1098 | }
1099 = $080D LDA #$00
1100 = $080F LDX #$00
1101 = $0811 LDY #$00
1102 = $0813 SEC
1103 = $0814 SBC #$07
1104 = $0816 SBC $084D
1105 = $0819 LDA $084D
1106 = $081C SBC #$02
1107 = $081E STA $084D
1108 = $0821 LDA $084D
1109 = $0824 SBC $084E
1110 = $0827 STA $084D
1111 = $082A LDA $084F
1112 = $082D SBC #$CF
1113 = $082F STA $084F
1114 = $0832 LDA $0850
1115 = $0835 SBC #$07
1116 = $0837 STA $0850
1117 = $083A LDA $084F
1118 = $083D SBC $0851
1119 = $0840 STA $084F
1120 = $0843 LDA $0850
1121 = $0846 SBC $0852
1122 = $0849 STA $0850
1123 = $084C RTS
10001124
10011125 ### word operations
10021126
550550 | }
551551 = ok
552552
553 The label doesn't have to be defined yet at the point
554 in the program text where it is `goto`d.
555
553556 | define main routine {
554557 | goto foo
555558 | }
558561 | }
559562 = ok
560563
564 Syntactically, you can `goto` a vector.
565
561566 | vector routine foo
562567 |
563568 | define main routine {
565570 | }
566571 = ok
567572
573 But you can't `goto` a label that never gets defined.
574
568575 | define main routine {
569576 | goto foo
570577 | }
571578 ? SyntaxError
579
580 `goto` may only be the final instruction in a block.
581
582 | define bar routine trashes x, z, n {
583 | ld x, 200
584 | }
585 |
586 | define main routine trashes x, z, n {
587 | goto bar
588 | ld x, 0
589 | }
590 ? Expected '}', but found 'ld'
572591
573592 Buffers and pointers.
574593