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

Merge pull request #12 from catseye/save-block

Save block
This commit is contained in:
Chris Pressey 2018-05-08 10:49:14 +01:00 committed by GitHub
commit 9536dd011f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 455 additions and 7 deletions

View File

@ -1,6 +1,6 @@
# encoding: UTF-8
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.model import (
TYPE_BYTE, TYPE_WORD,
TableType, BufferType, PointerType, VectorType, RoutineType,
@ -269,7 +269,7 @@ class Context(object):
self._writeable.remove(ref)
def set_writeable(self, *refs):
"""Intended to be used for implementing analyzing `for`."""
"""Intended to be used for implementing analyzing `for`, but also used in `save`."""
for ref in refs:
self._writeable.add(ref)
@ -292,6 +292,41 @@ class Context(object):
self.assert_in_range(dest.index, dest.ref)
self.set_written(dest.ref)
def extract(self, location):
"""Sets the given location as writeable in the context, and returns a 'baton' representing
the previous state of context for that location. This 'baton' can be used to later restore
this state of context."""
# Used in `save`.
baton = (
location,
location in self._touched,
self._range.get(location, None),
location in self._writeable,
)
self.set_writeable(location)
return baton
def re_introduce(self, baton):
"""Given a 'baton' produced by `extract()`, restores the context for that saved location
to what it was before `extract()` was called."""
# Used in `save`.
location, was_touched, was_range, was_writeable = baton
if was_touched:
self._touched.add(location)
elif location in self._touched:
self._touched.remove(location)
if was_range is not None:
self._range[location] = was_range
elif location in self._range:
del self._range[location]
if was_writeable:
self._writeable.add(location)
elif location in self._writeable:
self._writeable.remove(location)
class Analyzer(object):
@ -300,9 +335,9 @@ class Analyzer(object):
self.routines = {}
self.debug = debug
def assert_type(self, type, *locations):
def assert_type(self, type_, *locations):
for location in locations:
if location.type != type:
if location.type != type_:
raise TypeMismatchError(self.current_routine, location.name)
def assert_affected_within(self, name, affecting_type, limiting_type):
@ -385,6 +420,10 @@ class Analyzer(object):
self.analyze_for(instr, context)
elif isinstance(instr, WithInterruptsOff):
self.analyze_block(instr.block, context)
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
elif isinstance(instr, Save):
self.analyze_save(instr, context)
else:
raise NotImplementedError
@ -427,7 +466,7 @@ class Analyzer(object):
context.assert_meaningful(dest.ref, REG_Y)
context.set_written(dest.ref)
elif src.type != dest.type:
raise TypeMismatchError(instr, '{} and {}'.format(src, name))
raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
else:
context.set_written(dest)
# FIXME: context.copy_range(src, dest) ?
@ -730,3 +769,21 @@ class Analyzer(object):
# after it is executed, we know the range of the loop variable.
context.set_range(instr.dest, instr.final, instr.final)
context.set_writeable(instr.dest)
def analyze_save(self, instr, context):
if len(instr.locations) != 1:
raise NotImplementedError("Only 1 location in save is supported right now")
location = instr.locations[0]
self.assert_type(TYPE_BYTE, location)
baton = context.extract(location)
self.analyze_block(instr.block, context)
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
context.re_introduce(baton)
if location == REG_A:
pass
else:
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)

View File

@ -90,3 +90,8 @@ class For(Instr):
class WithInterruptsOff(Instr):
child_attrs = ('block',)
class Save(Instr):
value_attrs = ('locations',)
child_attrs = ('block',)

View File

@ -1,6 +1,6 @@
# encoding: UTF-8
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.model import (
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
@ -12,6 +12,7 @@ from sixtypical.gen6502 import (
Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative,
LDA, LDX, LDY, STA, STX, STY,
TAX, TAY, TXA, TYA,
PHA, PLA,
CLC, SEC, ADC, SBC, ROL, ROR,
INC, INX, INY, DEC, DEX, DEY,
CMP, CPX, CPY, AND, ORA, EOR,
@ -169,6 +170,8 @@ class Compiler(object):
return self.compile_for(instr)
elif isinstance(instr, WithInterruptsOff):
return self.compile_with_interrupts_off(instr)
elif isinstance(instr, Save):
return self.compile_save(instr)
else:
raise NotImplementedError
@ -613,3 +616,29 @@ class Compiler(object):
self.emitter.emit(SEI())
self.compile_block(instr.block)
self.emitter.emit(CLI())
def compile_save(self, instr):
location = instr.locations[0]
if location == REG_A:
self.emitter.emit(PHA())
self.compile_block(instr.block)
self.emitter.emit(PLA())
elif location == REG_X:
self.emitter.emit(TXA())
self.emitter.emit(PHA())
self.compile_block(instr.block)
self.emitter.emit(PLA())
self.emitter.emit(TAX())
elif location == REG_Y:
self.emitter.emit(TYA())
self.emitter.emit(PHA())
self.compile_block(instr.block)
self.emitter.emit(PLA())
self.emitter.emit(TAY())
else:
src_label = self.get_label(location.name)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(PHA())
self.compile_block(instr.block)
self.emitter.emit(PLA())
self.emitter.emit(STA(Absolute(src_label)))

View File

@ -306,6 +306,18 @@ class ORA(Instruction):
}
class PHA(Instruction):
opcodes = {
Implied: 0x48,
}
class PLA(Instruction):
opcodes = {
Implied: 0x68,
}
class ROL(Instruction):
opcodes = {
Implied: 0x2a, # Accumulator

View File

@ -1,6 +1,6 @@
# encoding: UTF-8
from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff
from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save
from sixtypical.model import (
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
RoutineType, VectorType, TableType, BufferType, PointerType,
@ -470,6 +470,10 @@ class Parser(object):
self.scanner.expect("off")
block = self.block()
return WithInterruptsOff(self.scanner.line_number, block=block)
elif self.scanner.consume("save"):
locations = self.locexprs()
block = self.block()
return Save(self.scanner.line_number, locations=locations, block=block)
elif self.scanner.consume("trash"):
dest = self.locexpr()
return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)

View File

@ -1938,6 +1938,290 @@ initialized at the start of that loop.
| }
? UnmeaningfulReadError: y
### save ###
Basic neutral test, where the `save` makes no difference.
| routine main
| inputs a, x
| outputs a, x
| trashes z, n
| {
| ld a, 1
| save x {
| ld a, 2
| }
| ld a, 3
| }
= ok
Saving any location (other than `a`) will trash `a`.
| routine main
| inputs a, x
| outputs a, x
| trashes z, n
| {
| ld a, 1
| save x {
| ld a, 2
| }
| }
? UnmeaningfulOutputError
Saving `a` does not trash anything.
| routine main
| inputs a, x
| outputs a, x
| trashes z, n
| {
| ld x, 1
| save a {
| ld x, 2
| }
| ld x, 3
| }
= ok
A defined value that has been saved can be trashed inside the block.
It will continue to be defined outside the block.
| routine main
| outputs x, y
| trashes a, z, n
| {
| ld x, 0
| save x {
| ld y, 0
| trash x
| }
| }
= ok
A trashed value that has been saved can be used inside the block.
It will continue to be trashed outside the block.
| routine main
| inputs a
| outputs a, x
| trashes z, n
| {
| ld x, 0
| trash x
| save x {
| ld a, 0
| ld x, 1
| }
| }
? UnmeaningfulOutputError: x
The known range of a value will be preserved outside the block as well.
| word one: 77
| word table[32] many
|
| routine main
| inputs a, many, one
| outputs many, one
| trashes a, x, n, z
| {
| and a, 31
| ld x, a
| save x {
| ld x, 255
| }
| copy one, many + x
| copy many + x, one
| }
= ok
| word one: 77
| word table[32] many
|
| routine main
| inputs a, many, one
| outputs many, one
| trashes a, x, n, z
| {
| and a, 63
| ld x, a
| save x {
| ld x, 1
| }
| copy one, many + x
| copy many + x, one
| }
? RangeExceededError
The known properties of a value are preserved inside the block, too.
| word one: 77
| word table[32] many
|
| routine main
| inputs a, many, one
| outputs many, one
| trashes a, x, n, z
| {
| and a, 31
| ld x, a
| save x {
| copy one, many + x
| copy many + x, one
| }
| copy one, many + x
| copy many + x, one
| }
= ok
A value which is not output from the routine, is preserved by the
routine; and can appear in a `save` exactly because a `save` preserves it.
| routine main
| outputs y
| trashes a, z, n
| {
| save x {
| ld y, 0
| ld x, 1
| }
| }
= ok
Because saving anything except `a` trashes `a`, a common idiom is to save `a`
first in a nested series of `save`s.
| routine main
| inputs a
| outputs a
| trashes z, n
| {
| save a {
| save x {
| ld a, 0
| ld x, 1
| }
| }
| }
= ok
Not just registers, but also user-defined locations can be saved.
| byte foo
|
| routine main
| trashes a, z, n
| {
| save foo {
| st 5, foo
| }
| }
= ok
But only if they are bytes.
| word foo
|
| routine main
| trashes a, z, n
| {
| save foo {
| copy 555, foo
| }
| }
? TypeMismatchError
| byte table[16] tab
|
| routine main
| trashes a, y, z, n
| {
| save tab {
| ld y, 0
| st 5, tab + y
| }
| }
? TypeMismatchError
A `goto` cannot appear within a `save` block, even if it is otherwise in tail position.
| routine other
| trashes a, z, n
| {
| ld a, 0
| }
|
| routine main
| trashes a, z, n
| {
| ld a, 1
| save x {
| ld x, 2
| goto other
| }
| }
? IllegalJumpError
### with interrupts ###
| vector routine
| inputs x
| outputs x
| trashes z, n
| bar
|
| routine foo
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| routine main
| outputs bar
| trashes a, n, z
| {
| with interrupts off {
| copy foo, bar
| }
| }
= ok
A `goto` cannot appear within a `with interrupts` block, even if it is
otherwise in tail position.
| vector routine
| inputs x
| outputs x
| trashes z, n
| bar
|
| routine foo
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| routine other
| trashes bar, a, n, z
| {
| ld a, 0
| }
|
| routine main
| trashes bar, a, n, z
| {
| with interrupts off {
| copy foo, bar
| goto other
| }
| }
? IllegalJumpError
### copy ###
Can't `copy` from a memory location that isn't initialized.

View File

@ -579,6 +579,49 @@ Compiling `for ... down to`.
= $0815 BNE $080F
= $0817 RTS
Compiling `save`.
| routine main
| inputs a
| outputs a
| trashes z, n
| {
| save a {
| save x {
| ld a, 0
| ld x, 1
| }
| }
| }
= $080D PHA
= $080E TXA
= $080F PHA
= $0810 LDA #$00
= $0812 LDX #$01
= $0814 PLA
= $0815 TAX
= $0816 PLA
= $0817 RTS
Compiling `save` on a user-defined location.
| byte foo
| routine main
| trashes a, z, n
| {
| save foo {
| ld a, 0
| st a, foo
| }
| }
= $080D LDA $081B
= $0810 PHA
= $0811 LDA #$00
= $0813 STA $081B
= $0816 PLA
= $0817 STA $081B
= $081A RTS
Indexed access.
| byte one

View File

@ -161,6 +161,20 @@ Basic "open-faced for" loops, up and down.
| }
= ok
Other blocks.
| routine main trashes a, x, c, z, v {
| with interrupts off {
| save a, x, c {
| ld a, 0
| }
| }
| save a, x, c {
| ld a, 0
| }
| }
= ok
User-defined memory addresses of different types.
| byte byt