1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-06-18 03:29:32 +00:00

Constants (such as routines) cannot be given in call-constraints.

This commit is contained in:
Chris Pressey 2015-10-22 20:01:02 +01:00
parent 421727e107
commit b5763e84b4
4 changed files with 125 additions and 35 deletions

View File

@ -44,11 +44,11 @@ TODO
For 0.6:
* routines shouldn't need to be listed as inputs.
* A more involved demo for the C64 — one that sets up an interrupt.
For 0.7:
* always analyze before executing or compiling, unless told not to
* `word` type.
* `word table` type.
* `trash` instruction.

View File

@ -29,19 +29,27 @@ class ForbiddenWriteError(StaticAnalysisError):
pass
class InconsistentConstraintsError(StaticAnalysisError):
pass
class TypeMismatchError(StaticAnalysisError):
pass
class IncompatibleConstraintsError(StaticAnalysisError):
class IllegalJumpError(StaticAnalysisError):
pass
class IllegalJumpError(StaticAnalysisError):
class ConstraintsError(StaticAnalysisError):
pass
class ConstantConstraintError(ConstraintsError):
pass
class InconsistentConstraintsError(ConstraintsError):
pass
class IncompatibleConstraintsError(ConstraintsError):
pass
@ -57,31 +65,39 @@ class Context(object):
A location is writeable if it was listed in the outputs and trashes
lists of this routine.
"""
def __init__(self, routine, inputs, outputs, trashes):
def __init__(self, routines, routine, inputs, outputs, trashes):
self.routines = routines # Location -> AST node
self.routine = routine
self._touched = set()
self._meaningful = set()
self._writeable = set()
for ref in inputs:
if ref.is_constant():
raise ConstantConstraintError('%s in %s' % (ref.name, routine.name))
self._meaningful.add(ref)
output_names = set()
for ref in outputs:
if ref.is_constant():
raise ConstantConstraintError('%s in %s' % (ref.name, routine.name))
output_names.add(ref.name)
self._writeable.add(ref)
for ref in trashes:
if ref.is_constant():
raise ConstantConstraintError('%s in %s' % (ref.name, routine.name))
if ref.name in output_names:
raise InconsistentConstraintsError(ref.name)
raise InconsistentConstraintsError('%s in %s' % (ref.name, routine.name))
self._writeable.add(ref)
def clone(self):
c = Context(self.routine, [], [], [])
c = Context(self.routines, self.routine, [], [], [])
c._touched = set(self._touched)
c._meaningful = set(self._meaningful)
c._writeable = set(self._writeable)
return c
def set_from(self, c):
assert c.routines == self.routines
assert c.routine == self.routine
self._touched = set(c._touched)
self._meaningful = set(c._meaningful)
@ -98,7 +114,7 @@ class Context(object):
def assert_meaningful(self, *refs, **kwargs):
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
for ref in refs:
if isinstance(ref, ConstantRef):
if isinstance(ref, ConstantRef) or ref in self.routines:
pass
elif isinstance(ref, LocationRef):
if ref not in self._meaningful:
@ -141,14 +157,15 @@ class Analyzer(object):
def __init__(self):
self.current_routine = None
self.has_encountered_goto = False
self.routines = {}
def analyze_program(self, program):
assert isinstance(program, Program)
routines = {r.name: r for r in program.routines}
self.routines = {r.location: r for r in program.routines}
for routine in program.routines:
self.analyze_routine(routine, routines)
self.analyze_routine(routine)
def analyze_routine(self, routine, routines):
def analyze_routine(self, routine):
assert isinstance(routine, Routine)
self.current_routine = routine
self.has_encountered_goto = False
@ -156,8 +173,8 @@ class Analyzer(object):
# it's an extern, that's fine
return
type = routine.location.type
context = Context(routine, type.inputs, type.outputs, type.trashes)
self.analyze_block(routine.block, context, routines)
context = Context(self.routines, routine, type.inputs, type.outputs, type.trashes)
self.analyze_block(routine.block, context)
if not self.has_encountered_goto:
for ref in type.outputs:
context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
@ -167,14 +184,14 @@ class Analyzer(object):
raise ForbiddenWriteError(message)
self.current_routine = None
def analyze_block(self, block, context, routines):
def analyze_block(self, block, context):
assert isinstance(block, Block)
for i in block.instrs:
if self.has_encountered_goto:
raise IllegalJumpError(i)
self.analyze_instr(i, context, routines)
self.analyze_instr(i, context)
def analyze_instr(self, instr, context, routines):
def analyze_instr(self, instr, context):
assert isinstance(instr, Instr)
opcode = instr.opcode
dest = instr.dest
@ -228,9 +245,9 @@ class Analyzer(object):
elif opcode == 'if':
context1 = context.clone()
context2 = context.clone()
self.analyze_block(instr.block1, context1, routines)
self.analyze_block(instr.block1, context1)
if instr.block2 is not None:
self.analyze_block(instr.block2, context2, routines)
self.analyze_block(instr.block2, context2)
# TODO may we need to deal with touched separately here too?
# probably not; if it wasn't meaningful in the first place, it
# doesn't really matter if you modified it or not, coming out.
@ -242,11 +259,11 @@ class Analyzer(object):
elif opcode == 'repeat':
# it will always be executed at least once, so analyze it having
# been executed the first time.
self.analyze_block(instr.block, context, routines)
self.analyze_block(instr.block, context)
# now analyze it having been executed a second time, with the context
# of it having already been executed.
self.analyze_block(instr.block, context, routines)
self.analyze_block(instr.block, context)
# NB I *think* that's enough... but it might not be?
elif opcode == 'copy':
@ -275,7 +292,7 @@ class Analyzer(object):
context.set_touched(REG_A, FLAG_Z, FLAG_N)
context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
elif opcode == 'with-sei':
self.analyze_block(instr.block, context, routines)
self.analyze_block(instr.block, context)
elif opcode == 'goto':
location = instr.location
type = location.type

View File

@ -57,7 +57,11 @@ class VectorType(ExecutableType):
class Ref(object):
pass
def is_constant(self):
"""read-only means that the program cannot change the value
of a location. constant means that the value of the location
will not change during the lifetime of the program."""
raise NotImplementedError
class LocationRef(Ref):
@ -80,6 +84,9 @@ class LocationRef(Ref):
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name)
def is_constant(self):
return isinstance(self.type, RoutineType)
class ConstantRef(Ref):
def __init__(self, type, value):
@ -97,6 +104,9 @@ class ConstantRef(Ref):
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.value)
def is_constant(self):
return True
REG_A = LocationRef(TYPE_BYTE, 'a')
REG_X = LocationRef(TYPE_BYTE, 'x')

View File

@ -1048,7 +1048,8 @@ Unless of course you subsequently initialize them.
| }
= ok
You can copy the address of a routine into a vector, if that vector is declared appropriately.
Routines are constants. You need not, and in fact cannot, specify a constant
as an input to, an output of, or as a trashed value of a routine.
| vector vec
| inputs x
@ -1070,6 +1071,72 @@ You can copy the address of a routine into a vector, if that vector is declared
| {
| copy foo, vec
| }
? ConstantConstraintError: foo in main
| vector vec
| inputs x
| outputs x
| trashes z, n
|
| routine foo
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| routine main
| outputs vec, foo
| trashes a, z, n
| {
| copy foo, vec
| }
? ConstantConstraintError: foo in main
| vector vec
| inputs x
| outputs x
| trashes z, n
|
| routine foo
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| routine main
| outputs vec
| trashes a, z, n, foo
| {
| copy foo, vec
| }
? ConstantConstraintError: foo in main
You can copy the address of a routine into a vector, if that vector is
declared appropriately.
| vector vec
| inputs x
| outputs x
| trashes z, n
|
| routine foo
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| routine main
| outputs vec
| trashes a, z, n
| {
| copy foo, vec
| }
= ok
But not if the vector is declared inappropriately.
@ -1088,7 +1155,6 @@ But not if the vector is declared inappropriately.
| }
|
| routine main
| inputs foo
| outputs vec
| trashes a, z, n
| {
@ -1112,7 +1178,6 @@ Routines are read-only.
| }
|
| routine main
| inputs foo
| outputs vec
| trashes a, z, n
| {
@ -1128,7 +1193,7 @@ Indirect call.
| ld x, 200
| }
|
| routine main inputs bar outputs x, foo trashes a, z, n {
| routine main outputs x, foo trashes a, z, n {
| copy bar, foo
| call foo
| }
@ -1142,7 +1207,7 @@ Calling the vector does indeed trash the things the vector says it does.
| ld x, 200
| }
|
| routine main inputs bar outputs x, foo trashes z, n {
| routine main outputs x, foo trashes z, n {
| ld x, 0
| copy bar, foo
| call foo
@ -1242,7 +1307,7 @@ Indirect goto.
| ld x, 200
| }
|
| routine main inputs bar outputs x trashes foo, a, z, n {
| routine main outputs x trashes foo, a, z, n {
| copy bar, foo
| goto foo
| }
@ -1260,14 +1325,13 @@ vector says it does.
| }
|
| routine sub
| inputs bar
| trashes foo, a, x, z, n {
| ld x, 0
| copy bar, foo
| goto foo
| }
|
| routine main inputs bar
| routine main
| outputs a
| trashes foo, x, z, n {
| call sub
@ -1286,7 +1350,6 @@ vector says it does.
| }
|
| routine sub
| inputs bar
| outputs x
| trashes foo, a, z, n {
| ld x, 0
@ -1294,7 +1357,7 @@ vector says it does.
| goto foo
| }
|
| routine main inputs bar
| routine main
| outputs a
| trashes foo, x, z, n {
| call sub