incrdecr codegen

This commit is contained in:
Irmen de Jong 2018-02-03 01:53:07 +01:00
parent a560982b7e
commit d18876ee70
6 changed files with 78 additions and 63 deletions

View File

@ -5,12 +5,12 @@ This is the assembly code generator (from the parse tree)
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import contextlib
import math
import attr
from typing import Set, Callable
from ..datatypes import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE
from ..plyparse import Scope
from ..plyparse import Scope, AstNode
from ..compile import Zeropage
@ -18,6 +18,14 @@ class CodeError(Exception):
pass
@attr.s(repr=False, cmp=False)
class Context:
out = attr.ib(type=Callable)
stmt = attr.ib(type=AstNode)
scope = attr.ib(type=Scope)
floats_enabled = attr.ib(type=bool)
def to_hex(number: int) -> str:
# 0..15 -> "0".."15"
# 16..255 -> "$10".."$ff"

View File

@ -7,20 +7,24 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
from typing import Callable
from ..plyparse import Scope, Assignment, AugAssignment, Register, LiteralValue, SymbolName, VarDef
from . import CodeError, preserving_registers, to_hex
from ..datatypes import REGISTER_BYTES, REGISTER_WORDS, VarType, DataType
from . import CodeError, preserving_registers, to_hex, Context
from ..datatypes import REGISTER_BYTES, VarType
from ..compile import Zeropage
def generate_assignment(out: Callable, stmt: Assignment, scope: Scope) -> None:
out("\v\t\t\t; " + stmt.lineref)
out("\v; @todo assignment")
def generate_assignment(ctx: Context) -> None:
assert isinstance(ctx.stmt, Assignment)
ctx.out("\v\t\t\t; " + ctx.stmt.lineref)
ctx.out("\v; @todo assignment")
# @todo assignment
def generate_aug_assignment(out: Callable, stmt: AugAssignment, scope: Scope) -> None:
def generate_aug_assignment(ctx: Context) -> None:
# for instance: value += 3 (value = 0-255 for now)
# left: Register, SymbolName, or Dereference. right: Expression/LiteralValue
out = ctx.out
stmt = ctx.stmt
assert isinstance(stmt, AugAssignment)
out("\v\t\t\t; " + stmt.lineref)
lvalue = stmt.left
rvalue = stmt.right
@ -28,23 +32,23 @@ def generate_aug_assignment(out: Callable, stmt: AugAssignment, scope: Scope) ->
if isinstance(rvalue, LiteralValue):
if type(rvalue.value) is int:
if 0 <= rvalue.value <= 255:
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, rvalue.value, "", scope)
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, rvalue.value, "", ctx.scope)
else:
raise CodeError("aug. assignment value must be 0..255", rvalue)
else:
raise CodeError("constant integer literal or variable required for now", rvalue) # XXX
elif isinstance(rvalue, SymbolName):
symdef = scope.lookup(rvalue.name)
symdef = ctx.scope.lookup(rvalue.name)
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST and symdef.datatype.isinteger():
if 0 <= symdef.value.const_value() <= 255: # type: ignore
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, 0, symdef.name, scope)
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, 0, symdef.name, ctx.scope)
else:
raise CodeError("aug. assignment value must be 0..255", rvalue)
else:
raise CodeError("constant integer literal or variable required for now", rvalue) # XXX
elif isinstance(rvalue, Register):
# @todo check value range (single register; 0-255) @todo support combined registers
_generate_aug_reg_reg(out, lvalue, stmt.operator, rvalue, scope)
_generate_aug_reg_reg(out, lvalue, stmt.operator, rvalue, ctx.scope)
else:
# @todo Register += symbolname / dereference , _generate_aug_reg_mem?
raise CodeError("invalid rvalue for aug. assignment on register", rvalue)

View File

@ -5,13 +5,17 @@ This is the code generator for gotos and subroutine calls.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
from typing import Callable
from ..plyparse import Goto, SubCall
from . import Context
def generate_goto(out: Callable, stmt: Goto) -> None:
def generate_goto(ctx: Context) -> None:
stmt = ctx.stmt
assert isinstance(stmt, Goto)
pass # @todo
def generate_subcall(out: Callable, stmt: SubCall) -> None:
def generate_subcall(ctx: Context) -> None:
stmt = ctx.stmt
assert isinstance(stmt, SubCall)
pass # @todo

View File

@ -11,7 +11,7 @@ from typing import TextIO, Callable, no_type_check
from ..plylex import print_bold
from ..plyparse import Module, Scope, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions, \
InlineAssembly, Return, Register, Goto, SubCall, Assignment, AugAssignment, IncrDecr, AssignmentTargets
from . import CodeError, to_hex, to_mflpt5
from . import CodeError, to_hex, to_mflpt5, Context
from .variables import generate_block_init, generate_block_vars
from .assignment import generate_assignment, generate_aug_assignment
from .calls import generate_goto, generate_subcall
@ -131,6 +131,7 @@ class AssemblyGenerator:
# there's no code in the zero page block.
out("\v.pend\n")
for block in sorted(self.module.all_nodes(Block), key=lambda b: b.address or 0):
ctx = Context(out=out, stmt=None, scope=block.scope, floats_enabled=self.floats_enabled)
if block.name == "ZP":
continue # already processed
self.cur_block = block
@ -153,7 +154,8 @@ class AssemblyGenerator:
for stmt in block.scope.nodes:
if isinstance(stmt, (VarDef, Subroutine)):
continue # should have been handled already or will be later
self.generate_statement(out, stmt, block.scope)
ctx.stmt = stmt
self.generate_statement(ctx)
if block.name == "main" and isinstance(stmt, Label) and stmt.name == "start":
# make sure the main.start routine clears the decimal and carry flags as first steps
out("\vcld\n\vclc\n\vclv")
@ -170,8 +172,8 @@ class AssemblyGenerator:
out("\v; params: {}\n\v; returns: {} clobbers: {}".format(params or "-", returns or "-", clobbers or "-"))
cur_block = self.cur_block
self.cur_block = subdef.scope
for stmt in subdef.scope.nodes:
self.generate_statement(out, stmt, subdef.scope)
for ctx.stmt in subdef.scope.nodes:
self.generate_statement(ctx)
self.cur_block = cur_block
out("")
out("; -- end block subroutines")
@ -185,48 +187,52 @@ class AssemblyGenerator:
out("\n\v.pend\n")
@no_type_check
def generate_statement(self, out: Callable, stmt: AstNode, scope: Scope) -> None:
def generate_statement(self, ctx: Context) -> None:
stmt = ctx.stmt
if isinstance(stmt, Label):
out("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref))
ctx.out("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref))
elif isinstance(stmt, Return):
if stmt.value_A:
reg = Register(name="A", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_A)
generate_assignment(out, assignment, scope)
ctx.stmt = assignment
generate_assignment(ctx)
if stmt.value_X:
reg = Register(name="X", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_X)
generate_assignment(out, assignment, scope)
ctx.stmt = assignment
generate_assignment(ctx)
if stmt.value_Y:
reg = Register(name="Y", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_Y)
generate_assignment(out, assignment, scope)
out("\vrts")
ctx.stmt = assignment
generate_assignment(ctx)
ctx.out("\vrts")
elif isinstance(stmt, InlineAssembly):
out("\n\v; inline asm, " + stmt.lineref)
out(stmt.assembly)
out("\v; end inline asm, " + stmt.lineref + "\n")
ctx.out("\n\v; inline asm, " + stmt.lineref)
ctx.out(stmt.assembly)
ctx.out("\v; end inline asm, " + stmt.lineref + "\n")
elif isinstance(stmt, IncrDecr):
generate_incrdecr(out, stmt, scope, self.floats_enabled)
generate_incrdecr(ctx)
elif isinstance(stmt, Goto):
generate_goto(out, stmt)
generate_goto(ctx)
elif isinstance(stmt, SubCall):
generate_subcall(out, stmt)
generate_subcall(ctx)
elif isinstance(stmt, Assignment):
generate_assignment(out, stmt, scope)
generate_assignment(ctx)
elif isinstance(stmt, AugAssignment):
generate_aug_assignment(out, stmt, scope)
generate_aug_assignment(ctx)
elif isinstance(stmt, Directive):
if stmt.name == "breakpoint":
# put a marker in the source so that we can generate a list of breakpoints later
# this label is later extracted from the label dump file to turn it into a breakpoint instruction
out("_il65_breakpoint_{:d}".format(id(stmt)))
ctx.out("_il65_breakpoint_{:d}".format(id(stmt)))
# other directives are ignored here
else:
raise NotImplementedError("statement", stmt)

View File

@ -7,13 +7,16 @@ is quite frequent and this generates assembly code tweaked for this case.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
from typing import Callable
from ..plyparse import Scope, VarType, VarDef, Register, IncrDecr, SymbolName, Dereference, LiteralValue, datatype_of
from ..plyparse import VarType, VarDef, Register, IncrDecr, SymbolName, Dereference, LiteralValue, datatype_of
from ..datatypes import DataType, REGISTER_BYTES
from . import CodeError, preserving_registers, to_hex
from . import CodeError, preserving_registers, to_hex, Context
def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope, floats_enabled: bool) -> None:
def generate_incrdecr(ctx: Context) -> None:
out = ctx.out
stmt = ctx.stmt
scope = ctx.scope
assert isinstance(stmt, IncrDecr)
assert isinstance(stmt.howmuch, (int, float)) and stmt.howmuch >= 0
assert stmt.operator in ("++", "--")
if stmt.howmuch == 0:
@ -185,7 +188,7 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope, floats_enable
out("\vdec {:s}+1".format(what_str))
out("+")
elif target.datatype == DataType.FLOAT:
if not floats_enabled:
if not ctx.floats_enabled:
raise CodeError("floating point numbers not enabled via option")
if stmt.howmuch == 1.0:
# special case for +/-1
@ -224,7 +227,7 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope, floats_enable
what = target.operand.name
if stmt.howmuch == 1:
if target.datatype == DataType.FLOAT:
if not floats_enabled:
if not ctx.floats_enabled:
raise CodeError("floating point numbers not enabled via option")
with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True):
out("\vldx " + what)
@ -236,19 +239,13 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope, floats_enable
else:
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
out("\vlda " + what)
out("\vsta c64.SCRATCH_ZPWORD1")
out("\vlda {:s}+1".format(what))
out("\vsta c64.SCRATCH_ZPWORD1+1")
out("\vldy {:s}+1".format(what))
if target.datatype == DataType.BYTE:
if stmt.operator == "++":
out("\vjsr il65_lib.incr_deref_byte")
else:
out("\vjsr il65_lib.decr_deref_byte")
out("\vclc" if stmt.operator == "++" else "\vsec")
out("\vjsr il65_lib.incrdecr_deref_byte_reg_AY")
elif target.datatype == DataType.WORD:
if stmt.operator == "++":
out("\vjsr il65_lib.incr_deref_word")
else:
out("\vjsr il65_lib.decr_deref_word")
out("\vclc" if stmt.operator == "++" else "\vsec")
out("\vjsr il65_lib.incrdecr_deref_word_reg_AY")
else:
raise CodeError("cannot inc/decrement dereferenced literal of type " + str(target.datatype), stmt)
else:
@ -258,10 +255,7 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope, floats_enable
raise CodeError("can't dereference just a single register, need combined register", target)
reg = target.operand.name
if stmt.howmuch == 1:
if stmt.operator == "++":
out("\vclc")
else:
out("\vsec")
out("\vclc" if stmt.operator == "++" else "\vsec")
if target.datatype == DataType.BYTE:
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
out("\vjsr il65_lib.incrdecr_deref_byte_reg_" + reg)

View File

@ -31,17 +31,16 @@ start:
[$55 .float] ++
[screenm .float]--
;[border] ++
;[border] ++
;[border] --
[border] ++
[border] ++
[border] --
[$d020] ++
[$d020] ++
[$d020] --
;[border2] ++ ;@todo suport incr/decr on deref var WORD
;[border2] ++ ;@todo suport incr/decr on deref var WORD
;[border2] -- ;@todo suport incr/decr on deref var WORD
;[XY] ++
;[XY] --
[border2 .word] ++
[border2 .float] ++
[border2] --
[XY] ++
return 44.123
}