mirror of
https://github.com/catseye/SixtyPical.git
synced 2024-06-07 06:29:32 +00:00
commit
4b539930bd
27
.circleci/config.yml
Normal file
27
.circleci/config.yml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Python CircleCI 2.0 configuration file
|
||||||
|
#
|
||||||
|
# Check https://circleci.com/docs/2.0/language-python/ for more details
|
||||||
|
#
|
||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
docker:
|
||||||
|
- image: circleci/python:3.6.1
|
||||||
|
|
||||||
|
working_directory: ~/SixtyPical
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: install dependencies
|
||||||
|
command: |
|
||||||
|
echo "hi"
|
||||||
|
git clone https://github.com/catseye/Falderal
|
||||||
|
git clone https://github.com/catseye/dcc6502
|
||||||
|
(cd dcc6502 && make)
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: run tests
|
||||||
|
command: |
|
||||||
|
PATH=dcc6502:Falderal/bin:$PATH ./test.sh
|
26
HISTORY.md
26
HISTORY.md
|
@ -1,6 +1,32 @@
|
||||||
History of SixtyPical
|
History of SixtyPical
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
|
0.21
|
||||||
|
----
|
||||||
|
|
||||||
|
* A source file can be included in another source file
|
||||||
|
by means of the `include` directive.
|
||||||
|
* A routine can be declared `preserved`, which prevents a
|
||||||
|
compiler from omitting it from the final executable, even
|
||||||
|
if it determines it is not called by any other routine.
|
||||||
|
* The reference implementation constructs a callgraph and
|
||||||
|
determines the set of routines which are not reachable
|
||||||
|
(directly or indirectly) from `main`, with an eye to
|
||||||
|
omitting them from the final executable.
|
||||||
|
* Added `--prune-unreachable-routines` option, which causes
|
||||||
|
the compiler to in fact omit routines determined to be
|
||||||
|
unreachable as described above.
|
||||||
|
* Added `--include-path` option, which configures the list
|
||||||
|
of directories that are searched when a source file is
|
||||||
|
included with the `include` directive.
|
||||||
|
* Code generation now performs modest peephole optimization
|
||||||
|
at the end of each routine. This results in better code
|
||||||
|
generation for constructs in tail position, notably
|
||||||
|
tail optimization of `calls`, but also for `goto`s and
|
||||||
|
`if` blocks at the end of a routine.
|
||||||
|
* Began collecting architecture-specific and portable
|
||||||
|
include-files for a nascent "standard library".
|
||||||
|
|
||||||
0.20
|
0.20
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
11
README.md
11
README.md
|
@ -1,10 +1,12 @@
|
||||||
SixtyPical
|
SixtyPical
|
||||||
==========
|
==========
|
||||||
|
|
||||||
_Version 0.20. Work-in-progress, everything is subject to change._
|
_Version 0.21. Work-in-progress, everything is subject to change._
|
||||||
|
|
||||||
**SixtyPical** is a [low-level](#low-level) programming language
|
**SixtyPical** brings [extended static checking][] to the [6502][].
|
||||||
supporting a sophisticated [static analysis](#static-analysis).
|
|
||||||
|
SixtyPical is a [low-level](#low-level) programming language
|
||||||
|
supporting some advanced [static analysis](#static-analysis) methods.
|
||||||
Its reference compiler can generate [efficient code](#efficient-code) for
|
Its reference compiler can generate [efficient code](#efficient-code) for
|
||||||
several 6502-based [target platforms](#target-platforms) while catching many
|
several 6502-based [target platforms](#target-platforms) while catching many
|
||||||
common mistakes at compile-time, reducing the time spent in debugging.
|
common mistakes at compile-time, reducing the time spent in debugging.
|
||||||
|
@ -114,6 +116,7 @@ In order to run the tests for compilation, [dcc6502][] needs to be installed.
|
||||||
* [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md)
|
* [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md)
|
||||||
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
||||||
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
||||||
|
* [Literate test suite for SixtyPical callgraph construction](tests/SixtyPical%20Callgraph.md)
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
@ -124,7 +127,9 @@ Documentation
|
||||||
* [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
|
* [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
|
||||||
* [TODO](TODO.md)
|
* [TODO](TODO.md)
|
||||||
|
|
||||||
|
[6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
|
||||||
[MOS Technology 6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
|
[MOS Technology 6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
|
||||||
|
[extended static checking]: https://en.wikipedia.org/wiki/Extended_static_checking
|
||||||
[effect system]: https://en.wikipedia.org/wiki/Effect_system
|
[effect system]: https://en.wikipedia.org/wiki/Effect_system
|
||||||
[abstractly interprets]: https://en.wikipedia.org/wiki/Abstract_interpretation
|
[abstractly interprets]: https://en.wikipedia.org/wiki/Abstract_interpretation
|
||||||
[calling conventions]: https://en.wikipedia.org/wiki/Calling_convention
|
[calling conventions]: https://en.wikipedia.org/wiki/Calling_convention
|
||||||
|
|
57
TODO.md
57
TODO.md
|
@ -31,14 +31,6 @@ For goodness sake, let the programmer say `'A'` instead of `65`.
|
||||||
Not all computers think `'A'` should be `65`. Allow the character set to be
|
Not all computers think `'A'` should be `65`. Allow the character set to be
|
||||||
mapped. Probably copy what Ophis does.
|
mapped. Probably copy what Ophis does.
|
||||||
|
|
||||||
### "Include" directives
|
|
||||||
|
|
||||||
Search a searchlist of include paths. And use them to make libraries of routines.
|
|
||||||
|
|
||||||
One such library routine might be an `interrupt routine` type for various architectures.
|
|
||||||
Since "the supervisor" has stored values on the stack, we should be able to trash them
|
|
||||||
with impunity, in such a routine.
|
|
||||||
|
|
||||||
### Pointers into non-byte tables
|
### Pointers into non-byte tables
|
||||||
|
|
||||||
Right now you cannot get a pointer into a non-byte (for instance, word or vector) table.
|
Right now you cannot get a pointer into a non-byte (for instance, word or vector) table.
|
||||||
|
@ -63,6 +55,14 @@ What happens if a routine calls itself, directly or indirectly? Many
|
||||||
constraints might be violated in this case. We should probably disallow
|
constraints might be violated in this case. We should probably disallow
|
||||||
recursion by default. (Which means assembling the callgraph in all cases.)
|
recursion by default. (Which means assembling the callgraph in all cases.)
|
||||||
|
|
||||||
|
However note, it's okay for a routine to goto itself. It's a common
|
||||||
|
pattern for implementing a state machine, for a routine to tail-goto a
|
||||||
|
vector, which might contain the address of the same routine.
|
||||||
|
|
||||||
|
The problems only come up, I think, when a routine calls itself re-entrantly.
|
||||||
|
|
||||||
|
So the callgraph would need to distinguish between these two cases.
|
||||||
|
|
||||||
### Analyze memory usage
|
### Analyze memory usage
|
||||||
|
|
||||||
If you define two variables that occupy the same address, an analysis error ought
|
If you define two variables that occupy the same address, an analysis error ought
|
||||||
|
@ -83,36 +83,31 @@ This is not just an impressive trick -- in the presence of local pointers, which
|
||||||
use up a word in zero-page, which we consider a precious resource, it allow those
|
use up a word in zero-page, which we consider a precious resource, it allow those
|
||||||
zero-page locations to be re-used.
|
zero-page locations to be re-used.
|
||||||
|
|
||||||
### 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,
|
|
||||||
if the block is in tail position. The constraints should iron out the same both ways.
|
|
||||||
|
|
||||||
As long as the routine has consistent type context every place it exits, that should be fine.
|
|
||||||
|
|
||||||
### Branch optimization in `if`
|
|
||||||
|
|
||||||
Currently the `if` generator is not smart enough to avoid generating silly
|
|
||||||
jump instructions. (See the Fallthru tests.) Improve it.
|
|
||||||
|
|
||||||
### Dead code removal
|
|
||||||
|
|
||||||
Once we have a call graph we can omit routines that we're sure aren't called.
|
|
||||||
|
|
||||||
This would let us use include-files and standard-libraries nicely: any
|
|
||||||
routines they define, but that you don't use, don't get included.
|
|
||||||
|
|
||||||
Analyzing the set of possible routines that a vector can take on would help
|
|
||||||
this immensely.
|
|
||||||
|
|
||||||
Implementation
|
Implementation
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
### Line numbers in analysis error messages
|
### Filename and line number in analysis error messages
|
||||||
|
|
||||||
For analysis errors, there is a line number, but it's the line of the routine
|
For analysis errors, there is a line number, but it's the line of the routine
|
||||||
after the routine in which the analysis error occurred. Fix this.
|
after the routine in which the analysis error occurred. Fix this.
|
||||||
|
|
||||||
|
### Better selection of options
|
||||||
|
|
||||||
|
`-O` should turn on the standard optimizations.
|
||||||
|
|
||||||
|
There should maybe be a flag to turn off tail-call optimization.
|
||||||
|
|
||||||
|
Some options should automatically add the appropriate architecture include
|
||||||
|
directory to the path.
|
||||||
|
|
||||||
|
Distribution
|
||||||
|
------------
|
||||||
|
|
||||||
|
### Demo game
|
||||||
|
|
||||||
|
Seems you're not be able to get killed unless you go off the top or bottom of
|
||||||
|
the screen? In particular, you cannot collide with a bar?
|
||||||
|
|
||||||
Blue-skying
|
Blue-skying
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
@ -17,23 +17,23 @@ from tempfile import NamedTemporaryFile
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from sixtypical.symtab import SymbolTable
|
from sixtypical.symtab import SymbolTable
|
||||||
from sixtypical.parser import Parser, merge_programs
|
from sixtypical.parser import Parser, load_program, merge_programs
|
||||||
from sixtypical.analyzer import Analyzer
|
from sixtypical.analyzer import Analyzer
|
||||||
|
from sixtypical.callgraph import construct_callgraph, prune_unreachable_routines
|
||||||
from sixtypical.outputter import outputter_class_for
|
from sixtypical.outputter import outputter_class_for
|
||||||
from sixtypical.compiler import Compiler
|
from sixtypical.compiler import Compiler
|
||||||
|
|
||||||
|
|
||||||
def process_input_files(filenames, options):
|
def process_input_files(filenames, options):
|
||||||
symtab = SymbolTable()
|
symtab = SymbolTable()
|
||||||
|
include_path = options.include_path.split(':')
|
||||||
|
|
||||||
programs = []
|
programs = []
|
||||||
|
|
||||||
for filename in options.filenames:
|
for filename in options.filenames:
|
||||||
text = open(filename).read()
|
program = load_program(filename, symtab, include_path, include_file=False)
|
||||||
parser = Parser(symtab, text, filename)
|
|
||||||
if options.debug:
|
if options.debug:
|
||||||
print(symtab)
|
print(symtab)
|
||||||
program = parser.program()
|
|
||||||
programs.append(program)
|
programs.append(program)
|
||||||
|
|
||||||
if options.parse_only:
|
if options.parse_only:
|
||||||
|
@ -47,25 +47,24 @@ def process_input_files(filenames, options):
|
||||||
analyzer.analyze_program(program)
|
analyzer.analyze_program(program)
|
||||||
finally:
|
finally:
|
||||||
if options.dump_exit_contexts:
|
if options.dump_exit_contexts:
|
||||||
sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ':')))
|
sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ': ')))
|
||||||
sys.stdout.write("\n")
|
sys.stdout.write("\n")
|
||||||
|
|
||||||
|
callgraph = construct_callgraph(program)
|
||||||
|
if options.dump_callgraph:
|
||||||
|
sys.stdout.write(json.dumps(callgraph, indent=4, sort_keys=True, separators=(',', ': ')))
|
||||||
|
if options.prune_unreachable_routines:
|
||||||
|
program = prune_unreachable_routines(program, callgraph)
|
||||||
|
|
||||||
compilation_roster = None
|
compilation_roster = None
|
||||||
if options.optimize_fallthru:
|
if options.optimize_fallthru:
|
||||||
from sixtypical.fallthru import FallthruAnalyzer
|
from sixtypical.fallthru import FallthruAnalyzer
|
||||||
|
|
||||||
def dump(data, label=None):
|
|
||||||
if not options.dump_fallthru_info:
|
|
||||||
return
|
|
||||||
if label:
|
|
||||||
sys.stdout.write("*** {}:\n".format(label))
|
|
||||||
sys.stdout.write(json.dumps(data, indent=4, sort_keys=True, separators=(',', ':')))
|
|
||||||
sys.stdout.write("\n")
|
|
||||||
|
|
||||||
fa = FallthruAnalyzer(symtab, debug=options.debug)
|
fa = FallthruAnalyzer(symtab, debug=options.debug)
|
||||||
fa.analyze_program(program)
|
fa.analyze_program(program)
|
||||||
compilation_roster = fa.serialize()
|
compilation_roster = fa.serialize()
|
||||||
dump(compilation_roster)
|
if options.dump_fallthru_info:
|
||||||
|
sys.stdout.write(json.dumps(compilation_roster, indent=4, sort_keys=True, separators=(',', ': ')))
|
||||||
|
|
||||||
if options.analyze_only or (options.output is None and not options.run_on):
|
if options.analyze_only or (options.output is None and not options.run_on):
|
||||||
return
|
return
|
||||||
|
@ -139,6 +138,12 @@ if __name__ == '__main__':
|
||||||
"Default: raw."
|
"Default: raw."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
argparser.add_argument(
|
||||||
|
"--include-path", "-I", type=str, metavar='PATH', default='.',
|
||||||
|
help="A colon-separated list of directories in which to look for "
|
||||||
|
"files which are included during `include` directives."
|
||||||
|
)
|
||||||
|
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
"--analyze-only",
|
"--analyze-only",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
|
@ -161,6 +166,17 @@ if __name__ == '__main__':
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program."
|
help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program."
|
||||||
)
|
)
|
||||||
|
argparser.add_argument(
|
||||||
|
"--prune-unreachable-routines",
|
||||||
|
action="store_true",
|
||||||
|
help="Omit code for unreachable routines (as determined by the callgraph) "
|
||||||
|
"from the final output."
|
||||||
|
)
|
||||||
|
argparser.add_argument(
|
||||||
|
"--dump-callgraph",
|
||||||
|
action="store_true",
|
||||||
|
help="Dump the call graph, in JSON, to stdout after analyzing the program."
|
||||||
|
)
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
"--parse-only",
|
"--parse-only",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
|
@ -185,7 +201,7 @@ if __name__ == '__main__':
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
"--version",
|
"--version",
|
||||||
action="version",
|
action="version",
|
||||||
version="%(prog)s 0.20"
|
version="%(prog)s 0.21"
|
||||||
)
|
)
|
||||||
|
|
||||||
options, unknown = argparser.parse_known_args(sys.argv[1:])
|
options, unknown = argparser.parse_known_args(sys.argv[1:])
|
||||||
|
|
29
eg/README.md
29
eg/README.md
|
@ -3,35 +3,18 @@ in subdirectories by machine architecture.
|
||||||
|
|
||||||
### rudiments
|
### rudiments
|
||||||
|
|
||||||
In the [rudiments](rudiments/) directory are programs which are not for
|
In the [rudiments](rudiments/) directory are programs which are
|
||||||
any particular machine, but meant to demonstrate the features of SixtyPical.
|
meant to demonstrate the elementary features of SixtyPical, and
|
||||||
Some are meant to fail and produce an error message. Others can run on
|
to serve as manual integration test cases. See
|
||||||
any architecture where there is a routine at 65490 which outputs the value
|
[the README in that directory](rudiments/README.md) for details.
|
||||||
of the accumulator as an ASCII character.
|
|
||||||
|
|
||||||
### c64
|
### c64
|
||||||
|
|
||||||
In the [c64](c64/) directory are programs that run on the Commodore 64.
|
In the [c64](c64/) directory are programs that run on the Commodore 64.
|
||||||
The directory itself contains some simple demos, for example
|
The directory itself contains some simple demos, for example
|
||||||
[hearts.60p](c64/hearts.60p), while there are subdirectories for more
|
[hearts.60p](c64/hearts.60p), while there are subdirectories for more
|
||||||
elaborate demos:
|
elaborate demos, like the flagship demo game. See
|
||||||
|
[the README in that directory](c64/README.md) for details.
|
||||||
* [demo-game](c64/demo-game/): a little game-like program written as a
|
|
||||||
"can we write something you'd see in practice?" test case for SixtyPical.
|
|
||||||
|
|
||||||
* [ribos](c64/ribos/): a well-commented example of a C64 raster interrupt
|
|
||||||
routine. Originally written with the P65 assembler (which has since
|
|
||||||
been reborn as [Ophis][]).
|
|
||||||
|
|
||||||
The second version of Ribos has been translated to SixtyPical.
|
|
||||||
|
|
||||||
* [petulant](c64/petulant/): "The PETulant Cursor", a tiny (44 bytes)
|
|
||||||
"display hack". Originally written in the late 80's. Rewritten with
|
|
||||||
the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
|
|
||||||
hint as to its nature).
|
|
||||||
|
|
||||||
Translated to SixtyPical (in 2018), after adding some optimizations
|
|
||||||
to the SixtyPical compiler, the resulting executable is still 44 bytes!
|
|
||||||
|
|
||||||
### vic20
|
### vic20
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
This directory contains SixtyPical example programs
|
This directory contains SixtyPical example programs
|
||||||
specifically for the Commodore 64.
|
specifically for the Commodore 64.
|
||||||
|
|
||||||
See the [README in the parent directory](../README.md) for
|
There are subdirectories for more elaborate demos:
|
||||||
more information on these example programs.
|
|
||||||
|
* [demo-game](demo-game/): a little game-like program written as a
|
||||||
|
"can we write something you'd see in practice?" test case for SixtyPical.
|
||||||
|
|
||||||
|
* [ribos](ribos/): a well-commented example of a C64 raster interrupt
|
||||||
|
routine. Originally written with the P65 assembler (which has since
|
||||||
|
been reborn as [Ophis][]).
|
||||||
|
|
||||||
|
The second version of Ribos has been translated to SixtyPical.
|
||||||
|
|
||||||
|
* [petulant](petulant/): "The PETulant Cursor", a tiny (44 bytes)
|
||||||
|
"display hack". Originally written in the late 80's. Rewritten with
|
||||||
|
the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
|
||||||
|
hint as to its nature).
|
||||||
|
|
||||||
|
Translated to SixtyPical (in 2018), after adding some optimizations
|
||||||
|
to the SixtyPical compiler, the resulting executable is still 44 bytes!
|
||||||
|
|
||||||
|
[Ophis]: http://michaelcmartin.github.io/Ophis/
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
// * Demo Game for SixtyPical *
|
// * Demo Game for SixtyPical *
|
||||||
// ****************************
|
// ****************************
|
||||||
|
|
||||||
|
include "joystick.60p"
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// Type Definitions
|
// Type Definitions
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
@ -56,7 +58,6 @@ byte vic_border @ 53280
|
||||||
byte vic_bg @ 53281
|
byte vic_bg @ 53281
|
||||||
byte table[2048] screen @ 1024
|
byte table[2048] screen @ 1024
|
||||||
byte table[2048] colormap @ 55296
|
byte table[2048] colormap @ 55296
|
||||||
byte joy2 @ $dc00
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// Global Variables
|
// Global Variables
|
||||||
|
@ -69,7 +70,6 @@ word pos
|
||||||
word new_pos
|
word new_pos
|
||||||
|
|
||||||
word table[256] actor_delta
|
word table[256] actor_delta
|
||||||
word delta
|
|
||||||
|
|
||||||
byte player_died
|
byte player_died
|
||||||
|
|
||||||
|
@ -103,71 +103,6 @@ vector game_state_routine
|
||||||
// Utility Routines
|
// Utility Routines
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
define read_stick routine
|
|
||||||
inputs joy2
|
|
||||||
outputs delta
|
|
||||||
trashes a, x, z, n
|
|
||||||
{
|
|
||||||
ld x, joy2
|
|
||||||
ld a, x
|
|
||||||
and a, 1 // up
|
|
||||||
if z {
|
|
||||||
copy $ffd8, delta // -40
|
|
||||||
} else {
|
|
||||||
ld a, x
|
|
||||||
and a, 2 // down
|
|
||||||
if z {
|
|
||||||
copy word 40, delta
|
|
||||||
} else {
|
|
||||||
ld a, x
|
|
||||||
and a, 4 // left
|
|
||||||
if z {
|
|
||||||
copy $ffff, delta // -1
|
|
||||||
} else {
|
|
||||||
ld a, x
|
|
||||||
and a, 8 // right
|
|
||||||
if z {
|
|
||||||
copy word 1, delta
|
|
||||||
} else {
|
|
||||||
copy word 0, delta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// You can repeatedly (i.e. as part of actor logic or an IRQ handler)
|
|
||||||
// call this routine.
|
|
||||||
// Upon return, if carry is set, the button was pressed then released.
|
|
||||||
|
|
||||||
define check_button routine
|
|
||||||
inputs joy2
|
|
||||||
outputs c
|
|
||||||
trashes a, z, n
|
|
||||||
static byte button_down : 0
|
|
||||||
{
|
|
||||||
ld a, button_down
|
|
||||||
if z {
|
|
||||||
ld a, joy2
|
|
||||||
and a, $10
|
|
||||||
if z {
|
|
||||||
ld a, 1
|
|
||||||
st a, button_down
|
|
||||||
}
|
|
||||||
st off, c
|
|
||||||
} else {
|
|
||||||
ld a, joy2
|
|
||||||
and a, $10
|
|
||||||
if not z {
|
|
||||||
ld a, 0
|
|
||||||
st a, button_down
|
|
||||||
st on, c
|
|
||||||
} else {
|
|
||||||
st off, c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
define clear_screen routine
|
define clear_screen routine
|
||||||
outputs screen, colormap
|
outputs screen, colormap
|
||||||
trashes a, y, c, n, z
|
trashes a, y, c, n, z
|
||||||
|
@ -425,7 +360,7 @@ define game_state_game_over game_state_routine
|
||||||
// * Main Game Loop Driver *
|
// * Main Game Loop Driver *
|
||||||
// *************************
|
// *************************
|
||||||
|
|
||||||
define our_cinv game_state_routine
|
define our_cinv preserved game_state_routine
|
||||||
{
|
{
|
||||||
goto dispatch_game_state
|
goto dispatch_game_state
|
||||||
}
|
}
|
||||||
|
|
23
eg/c64/demo-game/run.sh
Executable file
23
eg/c64/demo-game/run.sh
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script builds and runs the demo game. You need
|
||||||
|
# the VICE emulatore installed, in particular VICE's x64.
|
||||||
|
|
||||||
|
# You might want a `vicerc` file like the following:
|
||||||
|
# [C64]
|
||||||
|
# VICIIDoubleScan=0
|
||||||
|
# VICIIDoubleSize=0
|
||||||
|
# KeySet1NorthWest=0
|
||||||
|
# KeySet1North=273
|
||||||
|
# KeySet1NorthEast=0
|
||||||
|
# KeySet1East=275
|
||||||
|
# KeySet1SouthEast=0
|
||||||
|
# KeySet1South=274
|
||||||
|
# KeySet1SouthWest=0
|
||||||
|
# KeySet1West=276
|
||||||
|
# KeySet1Fire=306
|
||||||
|
# KeySetEnable=1
|
||||||
|
# JoyDevice1=0
|
||||||
|
# JoyDevice2=2
|
||||||
|
|
||||||
|
../../../bin/sixtypical --run-on x64 -I ../../../include/c64/ demo-game.60p
|
15
eg/c64/joystick-demo.60p
Normal file
15
eg/c64/joystick-demo.60p
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
include "joystick.60p"
|
||||||
|
|
||||||
|
word screen @ 1024
|
||||||
|
|
||||||
|
define main routine
|
||||||
|
inputs joy2
|
||||||
|
outputs delta
|
||||||
|
trashes a, x, z, n, screen
|
||||||
|
{
|
||||||
|
repeat {
|
||||||
|
call read_stick
|
||||||
|
copy delta, screen
|
||||||
|
ld a, 1
|
||||||
|
} until z
|
||||||
|
}
|
|
@ -2,16 +2,16 @@ This directory contains example sources which demonstrate
|
||||||
the rudiments of SixtyPical.
|
the rudiments of SixtyPical.
|
||||||
|
|
||||||
Examples that 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.
|
when being compiled are in the `errorful/` subdirectory.
|
||||||
|
|
||||||
These files are intended to be architecture-agnostic.
|
The other sources are portable across architectures. They use
|
||||||
For the ones that do produce output, an appropriate source
|
`include` directives to bring in architecture-dependent libraries
|
||||||
under `support/` should be included first, so that system entry
|
to produce output. Libraries for such are provided in the
|
||||||
points such as `chrout` are defined. In addition, some of these
|
architecture-specific subdirectories of the `include` directory
|
||||||
programs use "standard" support modules, so those should be included
|
in the root directory of this repository; make sure it is on the
|
||||||
first too. For example:
|
compiler's include search path. For example:
|
||||||
|
|
||||||
sixtypical --run-on=x64 support/c64.60p support/stdlib.60p vector-table.60p
|
sixtypical --run-on=x64 -I../../include/c64/:../../include/stdlib/ vector-table.60p
|
||||||
|
|
||||||
`chrout` is a routine with outputs the value of the accumulator
|
`chrout` is a routine with outputs the value of the accumulator
|
||||||
as an ASCII character, disturbing none of the other registers,
|
as an ASCII character, disturbing none of the other registers,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print YY
|
// Should print YY
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
word score
|
word score
|
||||||
|
|
||||||
define main routine
|
define main routine
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print Y
|
// Should print Y
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
byte table[2048] buf
|
byte table[2048] buf
|
||||||
pointer ptr @ 254
|
pointer ptr @ 254
|
||||||
byte foo
|
byte foo
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print AA
|
// Should print AA
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
define print routine
|
define print routine
|
||||||
trashes a, z, n
|
trashes a, z, n
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print ENGGL
|
// Should print ENGGL
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
byte b
|
byte b
|
||||||
|
|
||||||
define main routine
|
define main routine
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print ENGGL
|
// Should print ENGGL
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
word w1
|
word w1
|
||||||
|
|
||||||
define main routine
|
define main routine
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print ENGGL
|
// Should print ENGGL
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
word w1
|
word w1
|
||||||
word w2
|
word w2
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Demonstrates vector tables.
|
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print YN
|
// Should print YN
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
define main routine
|
define main routine
|
||||||
trashes a, x, y, z, n, c, v
|
trashes a, x, y, z, n, c, v
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print YA
|
// Should print YA
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
define main routine
|
define main routine
|
||||||
trashes a, x, y, z, n, c, v
|
trashes a, x, y, z, n, c, v
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// should fail analysis with an UnmeaningfulReadError
|
||||||
|
// because adding 4 to the accumulator reads the carry
|
||||||
|
// but the carry has neither been provided as input
|
||||||
|
// nor set to anything in particular by this routine.
|
||||||
|
|
||||||
define add_four routine
|
define add_four routine
|
||||||
inputs a
|
inputs a
|
||||||
outputs a
|
outputs a
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// should fail analysis with a RangeExceededError
|
||||||
|
// because the index is detected to fall outside the
|
||||||
|
// allowable range of the table it is indexing.
|
||||||
|
|
||||||
byte table[8] message : "WHAT?"
|
byte table[8] message : "WHAT?"
|
||||||
|
|
||||||
define main routine
|
define main routine
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
vector vec
|
// should fail analysis with a ConstantConstraintError
|
||||||
|
// because it cannot copy the address of `foo` into `vec`
|
||||||
|
// because it has incompatible constraints.
|
||||||
|
|
||||||
|
vector routine
|
||||||
inputs y
|
inputs y
|
||||||
outputs y
|
outputs y
|
||||||
trashes z, n
|
trashes z, n
|
||||||
|
vec
|
||||||
|
|
||||||
routine foo
|
define foo routine
|
||||||
inputs x
|
inputs x
|
||||||
outputs x
|
outputs x
|
||||||
trashes z, n
|
trashes z, n
|
||||||
|
@ -11,7 +16,7 @@ routine foo
|
||||||
inc x
|
inc x
|
||||||
}
|
}
|
||||||
|
|
||||||
routine main
|
define main routine
|
||||||
inputs foo
|
inputs foo
|
||||||
outputs vec
|
outputs vec
|
||||||
trashes a, z, n
|
trashes a, z, n
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Include `support/${PLATFORM}.60p` and `support/stdlib.60p` before this source
|
|
||||||
// Should print 01
|
// Should print 01
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
include "prbyte.60p"
|
||||||
|
|
||||||
byte lives
|
byte lives
|
||||||
|
|
||||||
define main routine
|
define main routine
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print AB
|
// Should print AB
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
define bar routine trashes a, z, n {
|
define bar routine trashes a, z, n {
|
||||||
ld a, 66
|
ld a, 66
|
||||||
call chrout
|
call chrout
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
// Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
define main routine
|
define main routine
|
||||||
trashes a, y, z, n, c
|
trashes a, y, z, n, c
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print AB
|
// Should print AB
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
byte foo
|
byte foo
|
||||||
|
|
||||||
define print routine
|
define print routine
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print H (being ASCII 72 = 8 * 9)
|
// Should print H (being ASCII 72 = 8 * 9)
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
// Increase y by 7, circuitously
|
// Increase y by 7, circuitously
|
||||||
//
|
//
|
||||||
define foo routine
|
define foo routine
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print A
|
// Should print A
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
define main routine
|
define main routine
|
||||||
inputs a
|
inputs a
|
||||||
trashes a, z, n
|
trashes a, z, n
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// Demonstrates vector tables.
|
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print AABAB
|
// Should print AABAB
|
||||||
|
|
||||||
|
// Demonstrates vector tables.
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
vector routine
|
vector routine
|
||||||
trashes a, z, n
|
trashes a, z, n
|
||||||
print
|
print
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print AB
|
// Should print AB
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
vector routine
|
vector routine
|
||||||
trashes a, z, n
|
trashes a, z, n
|
||||||
print
|
print
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Include `support/${PLATFORM}.60p` before this source
|
|
||||||
// Should print YY
|
// Should print YY
|
||||||
|
|
||||||
|
include "chrout.60p"
|
||||||
|
|
||||||
word one
|
word one
|
||||||
word table[256] many
|
word table[256] many
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
word screen @ 1024
|
|
||||||
byte joy2 @ $dc00
|
byte joy2 @ $dc00
|
||||||
|
|
||||||
word delta
|
word delta
|
||||||
|
|
||||||
|
// Read the joystick and compute the delta it represents
|
||||||
|
// in a row-based 40-column grid like the C64's screen.
|
||||||
|
|
||||||
define read_stick routine
|
define read_stick routine
|
||||||
inputs joy2
|
inputs joy2
|
||||||
outputs delta
|
outputs delta
|
||||||
|
@ -36,14 +38,34 @@ define read_stick routine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
define main routine
|
// You can repeatedly (i.e. as part of actor logic or an IRQ handler)
|
||||||
|
// call this routine.
|
||||||
|
// Upon return, if carry is set, the button was pressed then released.
|
||||||
|
|
||||||
|
define check_button routine
|
||||||
inputs joy2
|
inputs joy2
|
||||||
outputs delta
|
outputs c
|
||||||
trashes a, x, z, n, screen
|
trashes a, z, n
|
||||||
|
static byte button_down : 0
|
||||||
{
|
{
|
||||||
repeat {
|
ld a, button_down
|
||||||
call read_stick
|
if z {
|
||||||
copy delta, screen
|
ld a, joy2
|
||||||
ld a, 1
|
and a, $10
|
||||||
} until z
|
if z {
|
||||||
|
ld a, 1
|
||||||
|
st a, button_down
|
||||||
|
}
|
||||||
|
st off, c
|
||||||
|
} else {
|
||||||
|
ld a, joy2
|
||||||
|
and a, $10
|
||||||
|
if not z {
|
||||||
|
ld a, 0
|
||||||
|
st a, button_down
|
||||||
|
st on, c
|
||||||
|
} else {
|
||||||
|
st off, c
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -141,17 +141,23 @@ class Analyzer(object):
|
||||||
def analyze_program(self, program):
|
def analyze_program(self, program):
|
||||||
assert isinstance(program, Program)
|
assert isinstance(program, Program)
|
||||||
for routine in program.routines:
|
for routine in program.routines:
|
||||||
context = self.analyze_routine(routine)
|
routine.called_routines = set()
|
||||||
|
context, type_ = self.analyze_routine(routine)
|
||||||
|
if type_:
|
||||||
|
routine.routine_type = type_
|
||||||
routine.encountered_gotos = list(context.encountered_gotos()) if context else []
|
routine.encountered_gotos = list(context.encountered_gotos()) if context else []
|
||||||
|
routine.called_routines = list(routine.called_routines)
|
||||||
|
|
||||||
def analyze_routine(self, routine):
|
def analyze_routine(self, routine):
|
||||||
assert isinstance(routine, Routine)
|
assert isinstance(routine, Routine)
|
||||||
|
type_ = self.get_type_for_name(routine.name)
|
||||||
|
|
||||||
if routine.block is None:
|
if routine.block is None:
|
||||||
# it's an extern, that's fine
|
# it's an extern, that's fine
|
||||||
return None
|
return None, type_
|
||||||
|
|
||||||
self.current_routine = routine
|
self.current_routine = routine
|
||||||
type_ = self.get_type_for_name(routine.name)
|
|
||||||
context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes)
|
context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes)
|
||||||
|
|
||||||
# register any local statics as already-initialized
|
# register any local statics as already-initialized
|
||||||
|
@ -209,7 +215,7 @@ class Analyzer(object):
|
||||||
|
|
||||||
self.exit_contexts = None
|
self.exit_contexts = None
|
||||||
self.current_routine = None
|
self.current_routine = None
|
||||||
return context
|
return context, type_
|
||||||
|
|
||||||
def analyze_block(self, block, context):
|
def analyze_block(self, block, context):
|
||||||
assert isinstance(block, Block)
|
assert isinstance(block, Block)
|
||||||
|
@ -512,16 +518,19 @@ class Analyzer(object):
|
||||||
raise NotImplementedError(opcode)
|
raise NotImplementedError(opcode)
|
||||||
|
|
||||||
def analyze_call(self, instr, context):
|
def analyze_call(self, instr, context):
|
||||||
type = self.get_type(instr.location)
|
type_ = self.get_type(instr.location)
|
||||||
if not isinstance(type, (RoutineType, VectorType)):
|
if not isinstance(type_, (RoutineType, VectorType)):
|
||||||
raise TypeMismatchError(instr, instr.location.name)
|
raise TypeMismatchError(instr, instr.location.name)
|
||||||
if isinstance(type, VectorType):
|
|
||||||
type = type.of_type
|
self.current_routine.called_routines.add((instr.location, type_))
|
||||||
for ref in type.inputs:
|
|
||||||
|
if isinstance(type_, VectorType):
|
||||||
|
type_ = type_.of_type
|
||||||
|
for ref in type_.inputs:
|
||||||
context.assert_meaningful(ref)
|
context.assert_meaningful(ref)
|
||||||
for ref in type.outputs:
|
for ref in type_.outputs:
|
||||||
context.set_written(ref)
|
context.set_written(ref)
|
||||||
for ref in type.trashes:
|
for ref in type_.trashes:
|
||||||
context.assert_writeable(ref)
|
context.assert_writeable(ref)
|
||||||
context.set_touched(ref)
|
context.set_touched(ref)
|
||||||
context.set_unmeaningful(ref)
|
context.set_unmeaningful(ref)
|
||||||
|
@ -533,6 +542,8 @@ class Analyzer(object):
|
||||||
if not isinstance(type_, (RoutineType, VectorType)):
|
if not isinstance(type_, (RoutineType, VectorType)):
|
||||||
raise TypeMismatchError(instr, location.name)
|
raise TypeMismatchError(instr, location.name)
|
||||||
|
|
||||||
|
self.current_routine.called_routines.add((instr.location, type_))
|
||||||
|
|
||||||
# assert that the dest routine's inputs are all initialized
|
# assert that the dest routine's inputs are all initialized
|
||||||
if isinstance(type_, VectorType):
|
if isinstance(type_, VectorType):
|
||||||
type_ = type_.of_type
|
type_ = type_.of_type
|
||||||
|
|
68
src/sixtypical/callgraph.py
Normal file
68
src/sixtypical/callgraph.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
from sixtypical.ast import Program
|
||||||
|
from sixtypical.model import RoutineType, VectorType
|
||||||
|
|
||||||
|
|
||||||
|
def find_routines_matching_type(program, type_):
|
||||||
|
"""Return the subset of routines of the program whose value
|
||||||
|
may be assigned to a location of the given type_ (typically
|
||||||
|
a vector)."""
|
||||||
|
return [r for r in program.routines if RoutineType.executable_types_compatible(r.routine_type, type_)]
|
||||||
|
|
||||||
|
|
||||||
|
def mark_as_reachable(graph, routine_name):
|
||||||
|
node = graph[routine_name]
|
||||||
|
if node.get('reachable', False):
|
||||||
|
return
|
||||||
|
node['reachable'] = True
|
||||||
|
for next_routine_name in node['potentially-calls']:
|
||||||
|
mark_as_reachable(graph, next_routine_name)
|
||||||
|
|
||||||
|
|
||||||
|
def construct_callgraph(program):
|
||||||
|
graph = {}
|
||||||
|
|
||||||
|
for routine in program.routines:
|
||||||
|
potentially_calls = []
|
||||||
|
for (called_routine, called_routine_type) in routine.called_routines:
|
||||||
|
if isinstance(called_routine_type, RoutineType):
|
||||||
|
potentially_calls.append(called_routine.name)
|
||||||
|
elif isinstance(called_routine_type, VectorType):
|
||||||
|
for potentially_called in find_routines_matching_type(program, called_routine_type):
|
||||||
|
potentially_calls.append(potentially_called.name)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
|
graph[routine.name] = {
|
||||||
|
'potentially-calls': potentially_calls,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Symmetric closure
|
||||||
|
# (Note, this information isn't used anywhere yet)
|
||||||
|
|
||||||
|
for routine in program.routines:
|
||||||
|
potentially_called_by = []
|
||||||
|
for (name, node) in graph.items():
|
||||||
|
potential_calls = node['potentially-calls']
|
||||||
|
if routine.name in potential_calls:
|
||||||
|
potentially_called_by.append(name)
|
||||||
|
graph[routine.name]['potentially-called-by'] = potentially_called_by
|
||||||
|
|
||||||
|
# Root set
|
||||||
|
|
||||||
|
root_set = set()
|
||||||
|
|
||||||
|
for routine in program.routines:
|
||||||
|
if getattr(routine, 'preserved', False) or routine.name == 'main':
|
||||||
|
root_set.add(routine)
|
||||||
|
|
||||||
|
# Reachability
|
||||||
|
|
||||||
|
for routine in root_set:
|
||||||
|
mark_as_reachable(graph, routine.name)
|
||||||
|
|
||||||
|
return graph
|
||||||
|
|
||||||
|
|
||||||
|
def prune_unreachable_routines(program, callgraph):
|
||||||
|
return Program(1, defns=program.defns, routines=[
|
||||||
|
r for r in program.routines if callgraph[r.name].get('reachable', False)
|
||||||
|
])
|
|
@ -122,10 +122,11 @@ class Compiler(object):
|
||||||
compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
|
compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
|
||||||
|
|
||||||
for roster_row in compilation_roster:
|
for roster_row in compilation_roster:
|
||||||
for routine_name in roster_row[0:-1]:
|
for i, routine_name in enumerate(roster_row):
|
||||||
self.compile_routine(self.routines[routine_name], skip_final_goto=True)
|
if i < len(roster_row) - 1:
|
||||||
routine_name = roster_row[-1]
|
self.compile_routine(self.routines[routine_name], next_routine=self.routines[roster_row[i + 1]])
|
||||||
self.compile_routine(self.routines[routine_name])
|
else:
|
||||||
|
self.compile_routine(self.routines[routine_name])
|
||||||
|
|
||||||
for location, label in self.trampolines.items():
|
for location, label in self.trampolines.items():
|
||||||
self.emitter.resolve_label(label)
|
self.emitter.resolve_label(label)
|
||||||
|
@ -155,23 +156,45 @@ class Compiler(object):
|
||||||
if defn.initial is None and defn.addr is None:
|
if defn.initial is None and defn.addr is None:
|
||||||
self.emitter.resolve_bss_label(label)
|
self.emitter.resolve_bss_label(label)
|
||||||
|
|
||||||
def compile_routine(self, routine, skip_final_goto=False):
|
def compile_routine(self, routine, next_routine=None):
|
||||||
self.current_routine = routine
|
|
||||||
self.skip_final_goto = skip_final_goto
|
|
||||||
self.final_goto_seen = False
|
|
||||||
assert isinstance(routine, Routine)
|
assert isinstance(routine, Routine)
|
||||||
|
|
||||||
|
self.current_routine = routine
|
||||||
|
|
||||||
if routine.block:
|
if routine.block:
|
||||||
self.emitter.resolve_label(self.get_label(routine.name))
|
self.emitter.resolve_label(self.get_label(routine.name))
|
||||||
self.compile_block(routine.block)
|
self.compile_block(routine.block)
|
||||||
if not self.final_goto_seen:
|
|
||||||
|
needs_rts = True
|
||||||
|
last_op = self.emitter.get_tail()
|
||||||
|
|
||||||
|
if isinstance(last_op, JSR):
|
||||||
|
if isinstance(last_op.operand, Absolute):
|
||||||
|
if isinstance(last_op.operand.value, Label):
|
||||||
|
label = last_op.operand.value
|
||||||
|
self.emitter.retract()
|
||||||
|
self.emitter.emit(JMP(Absolute(label)))
|
||||||
|
last_op = self.emitter.get_tail()
|
||||||
|
|
||||||
|
if isinstance(last_op, JMP):
|
||||||
|
needs_rts = False
|
||||||
|
if isinstance(last_op.operand, Absolute):
|
||||||
|
if isinstance(last_op.operand.value, Label):
|
||||||
|
if next_routine and last_op.operand.value.name == next_routine.name:
|
||||||
|
self.emitter.retract()
|
||||||
|
|
||||||
|
if needs_rts:
|
||||||
self.emitter.emit(RTS())
|
self.emitter.emit(RTS())
|
||||||
|
|
||||||
self.current_routine = None
|
self.current_routine = None
|
||||||
self.skip_final_goto = False
|
|
||||||
|
|
||||||
def compile_block(self, block):
|
def compile_block(self, block):
|
||||||
assert isinstance(block, Block)
|
assert isinstance(block, Block)
|
||||||
|
block.shallow_contains_goto = False
|
||||||
for instr in block.instrs:
|
for instr in block.instrs:
|
||||||
self.compile_instr(instr)
|
self.compile_instr(instr)
|
||||||
|
if isinstance(instr, GoTo):
|
||||||
|
block.shallow_contains_goto = True
|
||||||
|
|
||||||
def compile_instr(self, instr):
|
def compile_instr(self, instr):
|
||||||
if isinstance(instr, SingleOp):
|
if isinstance(instr, SingleOp):
|
||||||
|
@ -437,19 +460,15 @@ class Compiler(object):
|
||||||
raise NotImplementedError(location_type)
|
raise NotImplementedError(location_type)
|
||||||
|
|
||||||
def compile_goto(self, instr):
|
def compile_goto(self, instr):
|
||||||
self.final_goto_seen = True
|
location = instr.location
|
||||||
if self.skip_final_goto:
|
label = self.get_label(instr.location.name)
|
||||||
pass
|
location_type = self.get_type(location)
|
||||||
|
if isinstance(location_type, RoutineType):
|
||||||
|
self.emitter.emit(JMP(Absolute(label)))
|
||||||
|
elif isinstance(location_type, VectorType):
|
||||||
|
self.emitter.emit(JMP(Indirect(label)))
|
||||||
else:
|
else:
|
||||||
location = instr.location
|
raise NotImplementedError(location_type)
|
||||||
label = self.get_label(instr.location.name)
|
|
||||||
location_type = self.get_type(location)
|
|
||||||
if isinstance(location_type, RoutineType):
|
|
||||||
self.emitter.emit(JMP(Absolute(label)))
|
|
||||||
elif isinstance(location_type, VectorType):
|
|
||||||
self.emitter.emit(JMP(Indirect(label)))
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(location_type)
|
|
||||||
|
|
||||||
def compile_cmp(self, instr, src, dest):
|
def compile_cmp(self, instr, src, dest):
|
||||||
"""`instr` is only for reporting purposes"""
|
"""`instr` is only for reporting purposes"""
|
||||||
|
@ -662,12 +681,17 @@ class Compiler(object):
|
||||||
else_label = Label('else_label')
|
else_label = Label('else_label')
|
||||||
self.emitter.emit(cls(Relative(else_label)))
|
self.emitter.emit(cls(Relative(else_label)))
|
||||||
self.compile_block(instr.block1)
|
self.compile_block(instr.block1)
|
||||||
|
|
||||||
if instr.block2 is not None:
|
if instr.block2 is not None:
|
||||||
end_label = Label('end_label')
|
if instr.block1.shallow_contains_goto:
|
||||||
self.emitter.emit(JMP(Absolute(end_label)))
|
self.emitter.resolve_label(else_label)
|
||||||
self.emitter.resolve_label(else_label)
|
self.compile_block(instr.block2)
|
||||||
self.compile_block(instr.block2)
|
else:
|
||||||
self.emitter.resolve_label(end_label)
|
end_label = Label('end_label')
|
||||||
|
self.emitter.emit(JMP(Absolute(end_label)))
|
||||||
|
self.emitter.resolve_label(else_label)
|
||||||
|
self.compile_block(instr.block2)
|
||||||
|
self.emitter.resolve_label(end_label)
|
||||||
else:
|
else:
|
||||||
self.emitter.resolve_label(else_label)
|
self.emitter.resolve_label(else_label)
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,16 @@ class Emitter(object):
|
||||||
self.accum.append(thing)
|
self.accum.append(thing)
|
||||||
self.addr += thing.size()
|
self.addr += thing.size()
|
||||||
|
|
||||||
|
def get_tail(self):
|
||||||
|
if self.accum:
|
||||||
|
return self.accum[-1]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def retract(self):
|
||||||
|
thing = self.accum.pop()
|
||||||
|
self.addr -= thing.size()
|
||||||
|
|
||||||
def serialize_to(self, stream):
|
def serialize_to(self, stream):
|
||||||
"""`stream` should be a file opened in binary mode."""
|
"""`stream` should be a file opened in binary mode."""
|
||||||
addr = self.start_addr
|
addr = self.start_addr
|
||||||
|
|
|
@ -21,8 +21,9 @@ class ForwardReference(object):
|
||||||
|
|
||||||
|
|
||||||
class Parser(object):
|
class Parser(object):
|
||||||
def __init__(self, symtab, text, filename):
|
def __init__(self, symtab, text, filename, include_path):
|
||||||
self.symtab = symtab
|
self.symtab = symtab
|
||||||
|
self.include_path = include_path
|
||||||
self.scanner = Scanner(text, filename)
|
self.scanner = Scanner(text, filename)
|
||||||
self.current_routine_name = None
|
self.current_routine_name = None
|
||||||
|
|
||||||
|
@ -96,6 +97,12 @@ class Parser(object):
|
||||||
def program(self):
|
def program(self):
|
||||||
defns = []
|
defns = []
|
||||||
routines = []
|
routines = []
|
||||||
|
includes = []
|
||||||
|
while self.scanner.consume('include'):
|
||||||
|
filename = self.scanner.token
|
||||||
|
self.scanner.scan()
|
||||||
|
program = load_program(filename, self.symtab, self.include_path, include_file=True)
|
||||||
|
includes.append(program)
|
||||||
while self.scanner.on('typedef', 'const'):
|
while self.scanner.on('typedef', 'const'):
|
||||||
if self.scanner.on('typedef'):
|
if self.scanner.on('typedef'):
|
||||||
self.typedef()
|
self.typedef()
|
||||||
|
@ -111,13 +118,20 @@ class Parser(object):
|
||||||
name = self.scanner.token
|
name = self.scanner.token
|
||||||
self.scanner.scan()
|
self.scanner.scan()
|
||||||
self.current_routine_name = name
|
self.current_routine_name = name
|
||||||
|
preserved = False
|
||||||
|
if self.scanner.consume('preserved'):
|
||||||
|
preserved = True
|
||||||
type_, routine = self.routine(name)
|
type_, routine = self.routine(name)
|
||||||
self.declare(name, routine, type_)
|
self.declare(name, routine, type_)
|
||||||
|
routine.preserved = preserved
|
||||||
routines.append(routine)
|
routines.append(routine)
|
||||||
self.current_routine_name = None
|
self.current_routine_name = None
|
||||||
self.scanner.check_type('EOF')
|
self.scanner.check_type('EOF')
|
||||||
|
|
||||||
program = Program(self.scanner.line_number, defns=defns, routines=routines)
|
program = Program(self.scanner.line_number, defns=defns, routines=routines)
|
||||||
|
programs = includes + [program]
|
||||||
|
program = merge_programs(programs)
|
||||||
|
|
||||||
self.resolve_symbols(program)
|
self.resolve_symbols(program)
|
||||||
return program
|
return program
|
||||||
|
|
||||||
|
@ -466,6 +480,19 @@ class Parser(object):
|
||||||
# - - - -
|
# - - - -
|
||||||
|
|
||||||
|
|
||||||
|
def load_program(filename, symtab, include_path, include_file=False):
|
||||||
|
import os
|
||||||
|
if include_file:
|
||||||
|
for include_dir in include_path:
|
||||||
|
if os.path.exists(os.path.join(include_dir, filename)):
|
||||||
|
filename = os.path.join(include_dir, filename)
|
||||||
|
break
|
||||||
|
text = open(filename).read()
|
||||||
|
parser = Parser(symtab, text, filename, include_path)
|
||||||
|
program = parser.program()
|
||||||
|
return program
|
||||||
|
|
||||||
|
|
||||||
def merge_programs(programs):
|
def merge_programs(programs):
|
||||||
"""Assumes that the programs do not have any conflicts."""
|
"""Assumes that the programs do not have any conflicts."""
|
||||||
|
|
||||||
|
|
1
test.sh
1
test.sh
|
@ -10,4 +10,5 @@ falderal --substring-error \
|
||||||
"tests/SixtyPical Storage.md" \
|
"tests/SixtyPical Storage.md" \
|
||||||
"tests/SixtyPical Control Flow.md" \
|
"tests/SixtyPical Control Flow.md" \
|
||||||
"tests/SixtyPical Fallthru.md" \
|
"tests/SixtyPical Fallthru.md" \
|
||||||
|
"tests/SixtyPical Callgraph.md" \
|
||||||
"tests/SixtyPical Compilation.md"
|
"tests/SixtyPical Compilation.md"
|
||||||
|
|
223
tests/SixtyPical Callgraph.md
Normal file
223
tests/SixtyPical Callgraph.md
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
SixtyPical Callgraph
|
||||||
|
====================
|
||||||
|
|
||||||
|
This is a test suite, written in [Falderal][] format, for the ability of
|
||||||
|
a SixtyPical analyzer to construct a callgraph of which routines call which
|
||||||
|
other routines, and its ability to discover which routines will never be
|
||||||
|
called.
|
||||||
|
|
||||||
|
[Falderal]: http://catseye.tc/node/Falderal
|
||||||
|
|
||||||
|
-> Tests for functionality "Dump callgraph info for SixtyPical program"
|
||||||
|
|
||||||
|
The `main` routine is always called. The thing that it will
|
||||||
|
be called by is the system, but the callgraph analyzer simply
|
||||||
|
considers it to be "reachable".
|
||||||
|
|
||||||
|
| define main routine
|
||||||
|
| {
|
||||||
|
| }
|
||||||
|
= {
|
||||||
|
= "main": {
|
||||||
|
= "potentially-called-by": [],
|
||||||
|
= "potentially-calls": [],
|
||||||
|
= "reachable": true
|
||||||
|
= }
|
||||||
|
= }
|
||||||
|
|
||||||
|
If a routine is called by another routine, this fact will be noted.
|
||||||
|
If it is reachable (directly or indirectly) from `main`, this will
|
||||||
|
be noted as well.
|
||||||
|
|
||||||
|
| define main routine
|
||||||
|
| {
|
||||||
|
| call other
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other routine
|
||||||
|
| {
|
||||||
|
| }
|
||||||
|
= {
|
||||||
|
= "main": {
|
||||||
|
= "potentially-called-by": [],
|
||||||
|
= "potentially-calls": [
|
||||||
|
= "other"
|
||||||
|
= ],
|
||||||
|
= "reachable": true
|
||||||
|
= },
|
||||||
|
= "other": {
|
||||||
|
= "potentially-called-by": [
|
||||||
|
= "main"
|
||||||
|
= ],
|
||||||
|
= "potentially-calls": [],
|
||||||
|
= "reachable": true
|
||||||
|
= }
|
||||||
|
= }
|
||||||
|
|
||||||
|
If a routine is not potentially called by any other routine that is
|
||||||
|
ultimately potentially called by `main`, this absence will be noted
|
||||||
|
— the routine will not be considered reachable — and a compiler or
|
||||||
|
linker will be permitted to omit it from the final executable.
|
||||||
|
|
||||||
|
| define main routine
|
||||||
|
| {
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other routine
|
||||||
|
| {
|
||||||
|
| }
|
||||||
|
= {
|
||||||
|
= "main": {
|
||||||
|
= "potentially-called-by": [],
|
||||||
|
= "potentially-calls": [],
|
||||||
|
= "reachable": true
|
||||||
|
= },
|
||||||
|
= "other": {
|
||||||
|
= "potentially-called-by": [],
|
||||||
|
= "potentially-calls": []
|
||||||
|
= }
|
||||||
|
= }
|
||||||
|
|
||||||
|
If a routine is not called by another routine, but it is declared
|
||||||
|
explicitly as `preserved`, then it will still be considered
|
||||||
|
reachable, and a compiler or linker will not be permitted to omit it
|
||||||
|
from the final executable. This is useful for interrupt routines
|
||||||
|
and such that really are used by some part of the system, even if
|
||||||
|
not directly by another SixtyPical routine.
|
||||||
|
|
||||||
|
| define main routine
|
||||||
|
| {
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other preserved routine
|
||||||
|
| {
|
||||||
|
| }
|
||||||
|
= {
|
||||||
|
= "main": {
|
||||||
|
= "potentially-called-by": [],
|
||||||
|
= "potentially-calls": [],
|
||||||
|
= "reachable": true
|
||||||
|
= },
|
||||||
|
= "other": {
|
||||||
|
= "potentially-called-by": [],
|
||||||
|
= "potentially-calls": [],
|
||||||
|
= "reachable": true
|
||||||
|
= }
|
||||||
|
= }
|
||||||
|
|
||||||
|
If a routine is called from a preserved routine, that routine is
|
||||||
|
reachable.
|
||||||
|
|
||||||
|
| define main routine
|
||||||
|
| {
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other1 preserved routine
|
||||||
|
| {
|
||||||
|
| call other2
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other2 preserved routine
|
||||||
|
| {
|
||||||
|
| }
|
||||||
|
= {
|
||||||
|
= "main": {
|
||||||
|
= "potentially-called-by": [],
|
||||||
|
= "potentially-calls": [],
|
||||||
|
= "reachable": true
|
||||||
|
= },
|
||||||
|
= "other1": {
|
||||||
|
= "potentially-called-by": [],
|
||||||
|
= "potentially-calls": [
|
||||||
|
= "other2"
|
||||||
|
= ],
|
||||||
|
= "reachable": true
|
||||||
|
= },
|
||||||
|
= "other2": {
|
||||||
|
= "potentially-called-by": [
|
||||||
|
= "other1"
|
||||||
|
= ],
|
||||||
|
= "potentially-calls": [],
|
||||||
|
= "reachable": true
|
||||||
|
= }
|
||||||
|
= }
|
||||||
|
|
||||||
|
If a group of routines potentially call each other, but neither is
|
||||||
|
found to be reachable (directly or indirectly) from `main` or a
|
||||||
|
`preserved` routine, the routines in the group will not be considered
|
||||||
|
reachable.
|
||||||
|
|
||||||
|
| define main routine
|
||||||
|
| {
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other1 routine
|
||||||
|
| {
|
||||||
|
| call other2
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other2 routine
|
||||||
|
| {
|
||||||
|
| call other1
|
||||||
|
| }
|
||||||
|
= {
|
||||||
|
= "main": {
|
||||||
|
= "potentially-called-by": [],
|
||||||
|
= "potentially-calls": [],
|
||||||
|
= "reachable": true
|
||||||
|
= },
|
||||||
|
= "other1": {
|
||||||
|
= "potentially-called-by": [
|
||||||
|
= "other2"
|
||||||
|
= ],
|
||||||
|
= "potentially-calls": [
|
||||||
|
= "other2"
|
||||||
|
= ]
|
||||||
|
= },
|
||||||
|
= "other2": {
|
||||||
|
= "potentially-called-by": [
|
||||||
|
= "other1"
|
||||||
|
= ],
|
||||||
|
= "potentially-calls": [
|
||||||
|
= "other1"
|
||||||
|
= ]
|
||||||
|
= }
|
||||||
|
= }
|
||||||
|
|
||||||
|
-> Tests for functionality "Compile SixtyPical program with unreachable routine removal"
|
||||||
|
|
||||||
|
Basic test for actually removing unreachable routines from the resulting
|
||||||
|
executable when compiling SixtyPical programs.
|
||||||
|
|
||||||
|
| define main routine outputs a trashes z, n
|
||||||
|
| {
|
||||||
|
| ld a, 100
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other1 routine
|
||||||
|
| {
|
||||||
|
| call other2
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other2 routine
|
||||||
|
| {
|
||||||
|
| call other1
|
||||||
|
| }
|
||||||
|
= $080D LDA #$64
|
||||||
|
= $080F RTS
|
||||||
|
|
||||||
|
Test that marking routine as `preserved` preserves it in the output.
|
||||||
|
|
||||||
|
| define main routine outputs a trashes z, n
|
||||||
|
| {
|
||||||
|
| ld a, 100
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define other preserved routine outputs a trashes z, n
|
||||||
|
| {
|
||||||
|
| ld a, 5
|
||||||
|
| }
|
||||||
|
= $080D LDA #$64
|
||||||
|
= $080F RTS
|
||||||
|
= $0810 LDA #$05
|
||||||
|
= $0812 RTS
|
|
@ -51,10 +51,12 @@ Call extern.
|
||||||
| {
|
| {
|
||||||
| ld a, 65
|
| ld a, 65
|
||||||
| call chrout
|
| call chrout
|
||||||
|
| ld a, 0
|
||||||
| }
|
| }
|
||||||
= $080D LDA #$41
|
= $080D LDA #$41
|
||||||
= $080F JSR $FFD2
|
= $080F JSR $FFD2
|
||||||
= $0812 RTS
|
= $0812 LDA #$00
|
||||||
|
= $0814 RTS
|
||||||
|
|
||||||
Call defined routine.
|
Call defined routine.
|
||||||
|
|
||||||
|
@ -71,13 +73,39 @@ Call defined routine.
|
||||||
| trashes a, x, y, z, n
|
| trashes a, x, y, z, n
|
||||||
| {
|
| {
|
||||||
| call foo
|
| call foo
|
||||||
|
| ld a, 1
|
||||||
| }
|
| }
|
||||||
= $080D JSR $0811
|
= $080D JSR $0813
|
||||||
= $0810 RTS
|
= $0810 LDA #$01
|
||||||
= $0811 LDA #$00
|
= $0812 RTS
|
||||||
= $0813 LDX #$00
|
= $0813 LDA #$00
|
||||||
= $0815 LDY #$00
|
= $0815 LDX #$00
|
||||||
= $0817 RTS
|
= $0817 LDY #$00
|
||||||
|
= $0819 RTS
|
||||||
|
|
||||||
|
Tail call is optimized into a jump.
|
||||||
|
|
||||||
|
| define foo routine
|
||||||
|
| outputs a, x, y
|
||||||
|
| trashes z, n
|
||||||
|
| {
|
||||||
|
| ld a, 0
|
||||||
|
| ld x, 0
|
||||||
|
| ld y, 0
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| define main routine
|
||||||
|
| trashes a, x, y, z, n
|
||||||
|
| {
|
||||||
|
| ld a, 1
|
||||||
|
| call foo
|
||||||
|
| }
|
||||||
|
= $080D LDA #$01
|
||||||
|
= $080F JMP $0812
|
||||||
|
= $0812 LDA #$00
|
||||||
|
= $0814 LDX #$00
|
||||||
|
= $0816 LDY #$00
|
||||||
|
= $0818 RTS
|
||||||
|
|
||||||
Access a defined memory location.
|
Access a defined memory location.
|
||||||
|
|
||||||
|
@ -610,7 +638,6 @@ Compiling `repeat forever`.
|
||||||
= $080D LDY #$41
|
= $080D LDY #$41
|
||||||
= $080F INY
|
= $080F INY
|
||||||
= $0810 JMP $080F
|
= $0810 JMP $080F
|
||||||
= $0813 RTS
|
|
||||||
|
|
||||||
The body of `repeat forever` can be empty.
|
The body of `repeat forever` can be empty.
|
||||||
|
|
||||||
|
@ -620,7 +647,6 @@ The body of `repeat forever` can be empty.
|
||||||
| } forever
|
| } forever
|
||||||
| }
|
| }
|
||||||
= $080D JMP $080D
|
= $080D JMP $080D
|
||||||
= $0810 RTS
|
|
||||||
|
|
||||||
Compiling `for ... up to`.
|
Compiling `for ... up to`.
|
||||||
|
|
||||||
|
@ -1055,7 +1081,7 @@ Copy word to word table and back, with constant offsets.
|
||||||
= $0848 STA $084D
|
= $0848 STA $084D
|
||||||
= $084B RTS
|
= $084B RTS
|
||||||
|
|
||||||
Indirect call.
|
Indirect call. TODO: we don't need the final RTS here, omit it.
|
||||||
|
|
||||||
| vector routine
|
| vector routine
|
||||||
| outputs x
|
| outputs x
|
||||||
|
@ -1076,16 +1102,15 @@ Indirect call.
|
||||||
| copy bar, foo
|
| copy bar, foo
|
||||||
| call foo
|
| call foo
|
||||||
| }
|
| }
|
||||||
= $080D LDA #$1B
|
= $080D LDA #$1A
|
||||||
= $080F STA $0822
|
= $080F STA $0821
|
||||||
= $0812 LDA #$08
|
= $0812 LDA #$08
|
||||||
= $0814 STA $0823
|
= $0814 STA $0822
|
||||||
= $0817 JSR $081E
|
= $0817 JMP $081D
|
||||||
= $081A RTS
|
= $081A LDX #$C8
|
||||||
= $081B LDX #$C8
|
= $081C RTS
|
||||||
= $081D RTS
|
= $081D JMP ($0821)
|
||||||
= $081E JMP ($0822)
|
= $0820 RTS
|
||||||
= $0821 RTS
|
|
||||||
|
|
||||||
Compiling `goto`. Note that no `RTS` is emitted after the `JMP`.
|
Compiling `goto`. Note that no `RTS` is emitted after the `JMP`.
|
||||||
|
|
||||||
|
@ -1139,28 +1164,27 @@ Copying to and from a vector table.
|
||||||
| call one
|
| call one
|
||||||
| }
|
| }
|
||||||
= $080D LDX #$00
|
= $080D LDX #$00
|
||||||
= $080F LDA #$3F
|
= $080F LDA #$3E
|
||||||
= $0811 STA $0846
|
= $0811 STA $0845
|
||||||
= $0814 LDA #$08
|
= $0814 LDA #$08
|
||||||
= $0816 STA $0847
|
= $0816 STA $0846
|
||||||
= $0819 LDA #$3F
|
= $0819 LDA #$3E
|
||||||
= $081B STA $0848,X
|
= $081B STA $0847,X
|
||||||
= $081E LDA #$08
|
= $081E LDA #$08
|
||||||
= $0820 STA $0948,X
|
= $0820 STA $0947,X
|
||||||
= $0823 LDA $0846
|
= $0823 LDA $0845
|
||||||
= $0826 STA $0848,X
|
= $0826 STA $0847,X
|
||||||
= $0829 LDA $0847
|
= $0829 LDA $0846
|
||||||
= $082C STA $0948,X
|
= $082C STA $0947,X
|
||||||
= $082F LDA $0848,X
|
= $082F LDA $0847,X
|
||||||
= $0832 STA $0846
|
= $0832 STA $0845
|
||||||
= $0835 LDA $0948,X
|
= $0835 LDA $0947,X
|
||||||
= $0838 STA $0847
|
= $0838 STA $0846
|
||||||
= $083B JSR $0842
|
= $083B JMP $0841
|
||||||
= $083E RTS
|
= $083E LDX #$C8
|
||||||
= $083F LDX #$C8
|
= $0840 RTS
|
||||||
= $0841 RTS
|
= $0841 JMP ($0845)
|
||||||
= $0842 JMP ($0846)
|
= $0844 RTS
|
||||||
= $0845 RTS
|
|
||||||
|
|
||||||
Copying to and from a vector table, with constant offsets.
|
Copying to and from a vector table, with constant offsets.
|
||||||
|
|
||||||
|
@ -1190,28 +1214,27 @@ Copying to and from a vector table, with constant offsets.
|
||||||
| call one
|
| call one
|
||||||
| }
|
| }
|
||||||
= $080D LDX #$00
|
= $080D LDX #$00
|
||||||
= $080F LDA #$3F
|
= $080F LDA #$3E
|
||||||
= $0811 STA $0846
|
= $0811 STA $0845
|
||||||
= $0814 LDA #$08
|
= $0814 LDA #$08
|
||||||
= $0816 STA $0847
|
= $0816 STA $0846
|
||||||
= $0819 LDA #$3F
|
= $0819 LDA #$3E
|
||||||
= $081B STA $0849,X
|
= $081B STA $0848,X
|
||||||
= $081E LDA #$08
|
= $081E LDA #$08
|
||||||
= $0820 STA $0949,X
|
= $0820 STA $0948,X
|
||||||
= $0823 LDA $0846
|
= $0823 LDA $0845
|
||||||
= $0826 STA $084A,X
|
= $0826 STA $0849,X
|
||||||
= $0829 LDA $0847
|
= $0829 LDA $0846
|
||||||
= $082C STA $094A,X
|
= $082C STA $0949,X
|
||||||
= $082F LDA $084B,X
|
= $082F LDA $084A,X
|
||||||
= $0832 STA $0846
|
= $0832 STA $0845
|
||||||
= $0835 LDA $094B,X
|
= $0835 LDA $094A,X
|
||||||
= $0838 STA $0847
|
= $0838 STA $0846
|
||||||
= $083B JSR $0842
|
= $083B JMP $0841
|
||||||
= $083E RTS
|
= $083E LDX #$C8
|
||||||
= $083F LDX #$C8
|
= $0840 RTS
|
||||||
= $0841 RTS
|
= $0841 JMP ($0845)
|
||||||
= $0842 JMP ($0846)
|
= $0844 RTS
|
||||||
= $0845 RTS
|
|
||||||
|
|
||||||
### add, sub
|
### add, sub
|
||||||
|
|
||||||
|
@ -1697,15 +1720,14 @@ just the same as initialized global storage locations are.
|
||||||
| ld x, t
|
| ld x, t
|
||||||
| call foo
|
| call foo
|
||||||
| }
|
| }
|
||||||
= $080D LDX $081F
|
= $080D LDX $081E
|
||||||
= $0810 JSR $0814
|
= $0810 JMP $0813
|
||||||
= $0813 RTS
|
= $0813 STX $081D
|
||||||
= $0814 STX $081E
|
= $0816 INC $081D
|
||||||
= $0817 INC $081E
|
= $0819 LDX $081D
|
||||||
= $081A LDX $081E
|
= $081C RTS
|
||||||
= $081D RTS
|
= $081D .byte $FF
|
||||||
= $081E .byte $FF
|
= $081E .byte $07
|
||||||
= $081F .byte $07
|
|
||||||
|
|
||||||
Memory locations defined local dynamic to a routine are allocated
|
Memory locations defined local dynamic to a routine are allocated
|
||||||
just the same as uninitialized global storage locations are.
|
just the same as uninitialized global storage locations are.
|
||||||
|
@ -1730,13 +1752,12 @@ just the same as uninitialized global storage locations are.
|
||||||
| call foo
|
| call foo
|
||||||
| }
|
| }
|
||||||
= $080D LDX #$00
|
= $080D LDX #$00
|
||||||
= $080F STX $0821
|
= $080F STX $0820
|
||||||
= $0812 JSR $0816
|
= $0812 JMP $0815
|
||||||
= $0815 RTS
|
= $0815 STX $081F
|
||||||
= $0816 STX $0820
|
= $0818 INC $081F
|
||||||
= $0819 INC $0820
|
= $081B LDX $081F
|
||||||
= $081C LDX $0820
|
= $081E RTS
|
||||||
= $081F RTS
|
|
||||||
|
|
||||||
Memory locations defined local dynamic to a routine are allocated
|
Memory locations defined local dynamic to a routine are allocated
|
||||||
just the same as uninitialized global storage locations are, even
|
just the same as uninitialized global storage locations are, even
|
||||||
|
@ -1763,9 +1784,8 @@ when declared with a fixed address.
|
||||||
| }
|
| }
|
||||||
= $080D LDX #$00
|
= $080D LDX #$00
|
||||||
= $080F STX $0401
|
= $080F STX $0401
|
||||||
= $0812 JSR $0816
|
= $0812 JMP $0815
|
||||||
= $0815 RTS
|
= $0815 STX $0400
|
||||||
= $0816 STX $0400
|
= $0818 INC $0400
|
||||||
= $0819 INC $0400
|
= $081B LDX $0400
|
||||||
= $081C LDX $0400
|
= $081E RTS
|
||||||
= $081F RTS
|
|
||||||
|
|
|
@ -384,11 +384,6 @@ It can optimize out one of the `goto`s if they are the same.
|
||||||
|
|
||||||
It cannot optimize out the `goto`s if they are different.
|
It cannot optimize out the `goto`s if they are different.
|
||||||
|
|
||||||
Note, this currently produces unfortunately unoptimized code,
|
|
||||||
because generating code for the "true" branch of an `if` always
|
|
||||||
generates a jump out of the `if`, even if the last instruction
|
|
||||||
in the "true" branch is a `goto`.
|
|
||||||
|
|
||||||
| define foo routine trashes a, z, n
|
| define foo routine trashes a, z, n
|
||||||
| {
|
| {
|
||||||
| ld a, 0
|
| ld a, 0
|
||||||
|
@ -411,11 +406,10 @@ in the "true" branch is a `goto`.
|
||||||
| }
|
| }
|
||||||
= $080D RTS
|
= $080D RTS
|
||||||
= $080E LDA #$00
|
= $080E LDA #$00
|
||||||
= $0810 BNE $081A
|
= $0810 BNE $0817
|
||||||
= $0812 LDA #$01
|
= $0812 LDA #$01
|
||||||
= $0814 JMP $081F
|
= $0814 JMP $081C
|
||||||
= $0817 JMP $081F
|
= $0817 LDA #$02
|
||||||
= $081A LDA #$02
|
= $0819 JMP $080D
|
||||||
= $081C JMP $080D
|
= $081C LDA #$FF
|
||||||
= $081F LDA #$FF
|
= $081E RTS
|
||||||
= $0821 RTS
|
|
||||||
|
|
|
@ -73,6 +73,18 @@ Extern routines
|
||||||
| @ 65487
|
| @ 65487
|
||||||
= ok
|
= ok
|
||||||
|
|
||||||
|
Preserved routine.
|
||||||
|
|
||||||
|
| define main routine {
|
||||||
|
| ld a, $ff
|
||||||
|
| add a, $01
|
||||||
|
| }
|
||||||
|
| define foo preserved routine {
|
||||||
|
| ld a, 0
|
||||||
|
| add a, 1
|
||||||
|
| }
|
||||||
|
= ok
|
||||||
|
|
||||||
Trash.
|
Trash.
|
||||||
|
|
||||||
| define main routine {
|
| define main routine {
|
||||||
|
|
|
@ -13,6 +13,12 @@ implementation, `sixtypical`, that is going to implement these functionalities.
|
||||||
-> Functionality "Compile SixtyPical program" is implemented by
|
-> Functionality "Compile SixtyPical program" is implemented by
|
||||||
-> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
-> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||||
|
|
||||||
|
-> Functionality "Dump callgraph info for SixtyPical program" is implemented by
|
||||||
|
-> shell command "bin/sixtypical --dump-callgraph --analyze-only --traceback %(test-body-file)"
|
||||||
|
|
||||||
|
-> Functionality "Compile SixtyPical program with unreachable routine removal" is implemented by
|
||||||
|
-> shell command "bin/sixtypical --output-format=c64-basic-prg --prune-unreachable-routines --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||||
|
|
||||||
-> Functionality "Dump fallthru info for SixtyPical program" is implemented by
|
-> Functionality "Dump fallthru info for SixtyPical program" is implemented by
|
||||||
-> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
|
-> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user