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 Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
""" """
import contextlib import contextlib
import math import math
import attr
from typing import Set, Callable from typing import Set, Callable
from ..datatypes import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE from ..datatypes import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE
from ..plyparse import Scope from ..plyparse import Scope, AstNode
from ..compile import Zeropage from ..compile import Zeropage
@ -18,6 +18,14 @@ class CodeError(Exception):
pass 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: def to_hex(number: int) -> str:
# 0..15 -> "0".."15" # 0..15 -> "0".."15"
# 16..255 -> "$10".."$ff" # 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 typing import Callable
from ..plyparse import Scope, Assignment, AugAssignment, Register, LiteralValue, SymbolName, VarDef from ..plyparse import Scope, Assignment, AugAssignment, Register, LiteralValue, SymbolName, VarDef
from . import CodeError, preserving_registers, to_hex from . import CodeError, preserving_registers, to_hex, Context
from ..datatypes import REGISTER_BYTES, REGISTER_WORDS, VarType, DataType from ..datatypes import REGISTER_BYTES, VarType
from ..compile import Zeropage from ..compile import Zeropage
def generate_assignment(out: Callable, stmt: Assignment, scope: Scope) -> None: def generate_assignment(ctx: Context) -> None:
out("\v\t\t\t; " + stmt.lineref) assert isinstance(ctx.stmt, Assignment)
out("\v; @todo assignment") ctx.out("\v\t\t\t; " + ctx.stmt.lineref)
ctx.out("\v; @todo assignment")
# @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) # for instance: value += 3 (value = 0-255 for now)
# left: Register, SymbolName, or Dereference. right: Expression/LiteralValue # 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) out("\v\t\t\t; " + stmt.lineref)
lvalue = stmt.left lvalue = stmt.left
rvalue = stmt.right rvalue = stmt.right
@ -28,23 +32,23 @@ def generate_aug_assignment(out: Callable, stmt: AugAssignment, scope: Scope) ->
if isinstance(rvalue, LiteralValue): if isinstance(rvalue, LiteralValue):
if type(rvalue.value) is int: if type(rvalue.value) is int:
if 0 <= rvalue.value <= 255: 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: else:
raise CodeError("aug. assignment value must be 0..255", rvalue) raise CodeError("aug. assignment value must be 0..255", rvalue)
else: else:
raise CodeError("constant integer literal or variable required for now", rvalue) # XXX raise CodeError("constant integer literal or variable required for now", rvalue) # XXX
elif isinstance(rvalue, SymbolName): 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 isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST and symdef.datatype.isinteger():
if 0 <= symdef.value.const_value() <= 255: # type: ignore 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: else:
raise CodeError("aug. assignment value must be 0..255", rvalue) raise CodeError("aug. assignment value must be 0..255", rvalue)
else: else:
raise CodeError("constant integer literal or variable required for now", rvalue) # XXX raise CodeError("constant integer literal or variable required for now", rvalue) # XXX
elif isinstance(rvalue, Register): elif isinstance(rvalue, Register):
# @todo check value range (single register; 0-255) @todo support combined registers # @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: else:
# @todo Register += symbolname / dereference , _generate_aug_reg_mem? # @todo Register += symbolname / dereference , _generate_aug_reg_mem?
raise CodeError("invalid rvalue for aug. assignment on register", rvalue) 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 Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
""" """
from typing import Callable
from ..plyparse import Goto, SubCall 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 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 pass # @todo

View File

@ -11,7 +11,7 @@ from typing import TextIO, Callable, no_type_check
from ..plylex import print_bold from ..plylex import print_bold
from ..plyparse import Module, Scope, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions, \ from ..plyparse import Module, Scope, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions, \
InlineAssembly, Return, Register, Goto, SubCall, Assignment, AugAssignment, IncrDecr, AssignmentTargets 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 .variables import generate_block_init, generate_block_vars
from .assignment import generate_assignment, generate_aug_assignment from .assignment import generate_assignment, generate_aug_assignment
from .calls import generate_goto, generate_subcall from .calls import generate_goto, generate_subcall
@ -131,6 +131,7 @@ class AssemblyGenerator:
# there's no code in the zero page block. # there's no code in the zero page block.
out("\v.pend\n") out("\v.pend\n")
for block in sorted(self.module.all_nodes(Block), key=lambda b: b.address or 0): 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": if block.name == "ZP":
continue # already processed continue # already processed
self.cur_block = block self.cur_block = block
@ -153,7 +154,8 @@ class AssemblyGenerator:
for stmt in block.scope.nodes: for stmt in block.scope.nodes:
if isinstance(stmt, (VarDef, Subroutine)): if isinstance(stmt, (VarDef, Subroutine)):
continue # should have been handled already or will be later 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": 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 # make sure the main.start routine clears the decimal and carry flags as first steps
out("\vcld\n\vclc\n\vclv") 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 "-")) out("\v; params: {}\n\v; returns: {} clobbers: {}".format(params or "-", returns or "-", clobbers or "-"))
cur_block = self.cur_block cur_block = self.cur_block
self.cur_block = subdef.scope self.cur_block = subdef.scope
for stmt in subdef.scope.nodes: for ctx.stmt in subdef.scope.nodes:
self.generate_statement(out, stmt, subdef.scope) self.generate_statement(ctx)
self.cur_block = cur_block self.cur_block = cur_block
out("") out("")
out("; -- end block subroutines") out("; -- end block subroutines")
@ -185,48 +187,52 @@ class AssemblyGenerator:
out("\n\v.pend\n") out("\n\v.pend\n")
@no_type_check @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): 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): elif isinstance(stmt, Return):
if stmt.value_A: if stmt.value_A:
reg = Register(name="A", sourceref=stmt.sourceref) reg = Register(name="A", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref) assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref)) assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_A) assignment.nodes.append(stmt.value_A)
generate_assignment(out, assignment, scope) ctx.stmt = assignment
generate_assignment(ctx)
if stmt.value_X: if stmt.value_X:
reg = Register(name="X", sourceref=stmt.sourceref) reg = Register(name="X", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref) assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref)) assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_X) assignment.nodes.append(stmt.value_X)
generate_assignment(out, assignment, scope) ctx.stmt = assignment
generate_assignment(ctx)
if stmt.value_Y: if stmt.value_Y:
reg = Register(name="Y", sourceref=stmt.sourceref) reg = Register(name="Y", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref) assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref)) assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_Y) assignment.nodes.append(stmt.value_Y)
generate_assignment(out, assignment, scope) ctx.stmt = assignment
out("\vrts") generate_assignment(ctx)
ctx.out("\vrts")
elif isinstance(stmt, InlineAssembly): elif isinstance(stmt, InlineAssembly):
out("\n\v; inline asm, " + stmt.lineref) ctx.out("\n\v; inline asm, " + stmt.lineref)
out(stmt.assembly) ctx.out(stmt.assembly)
out("\v; end inline asm, " + stmt.lineref + "\n") ctx.out("\v; end inline asm, " + stmt.lineref + "\n")
elif isinstance(stmt, IncrDecr): elif isinstance(stmt, IncrDecr):
generate_incrdecr(out, stmt, scope, self.floats_enabled) generate_incrdecr(ctx)
elif isinstance(stmt, Goto): elif isinstance(stmt, Goto):
generate_goto(out, stmt) generate_goto(ctx)
elif isinstance(stmt, SubCall): elif isinstance(stmt, SubCall):
generate_subcall(out, stmt) generate_subcall(ctx)
elif isinstance(stmt, Assignment): elif isinstance(stmt, Assignment):
generate_assignment(out, stmt, scope) generate_assignment(ctx)
elif isinstance(stmt, AugAssignment): elif isinstance(stmt, AugAssignment):
generate_aug_assignment(out, stmt, scope) generate_aug_assignment(ctx)
elif isinstance(stmt, Directive): elif isinstance(stmt, Directive):
if stmt.name == "breakpoint": if stmt.name == "breakpoint":
# put a marker in the source so that we can generate a list of breakpoints later # 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 # 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 # other directives are ignored here
else: else:
raise NotImplementedError("statement", stmt) 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 Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
""" """
from typing import Callable from ..plyparse import VarType, VarDef, Register, IncrDecr, SymbolName, Dereference, LiteralValue, datatype_of
from ..plyparse import Scope, VarType, VarDef, Register, IncrDecr, SymbolName, Dereference, LiteralValue, datatype_of
from ..datatypes import DataType, REGISTER_BYTES 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 isinstance(stmt.howmuch, (int, float)) and stmt.howmuch >= 0
assert stmt.operator in ("++", "--") assert stmt.operator in ("++", "--")
if stmt.howmuch == 0: 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("\vdec {:s}+1".format(what_str))
out("+") out("+")
elif target.datatype == DataType.FLOAT: elif target.datatype == DataType.FLOAT:
if not floats_enabled: if not ctx.floats_enabled:
raise CodeError("floating point numbers not enabled via option") raise CodeError("floating point numbers not enabled via option")
if stmt.howmuch == 1.0: if stmt.howmuch == 1.0:
# special case for +/-1 # special case for +/-1
@ -224,7 +227,7 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope, floats_enable
what = target.operand.name what = target.operand.name
if stmt.howmuch == 1: if stmt.howmuch == 1:
if target.datatype == DataType.FLOAT: if target.datatype == DataType.FLOAT:
if not floats_enabled: if not ctx.floats_enabled:
raise CodeError("floating point numbers not enabled via option") raise CodeError("floating point numbers not enabled via option")
with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True): with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True):
out("\vldx " + what) out("\vldx " + what)
@ -236,19 +239,13 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope, floats_enable
else: else:
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True): with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
out("\vlda " + what) out("\vlda " + what)
out("\vsta c64.SCRATCH_ZPWORD1") out("\vldy {:s}+1".format(what))
out("\vlda {:s}+1".format(what))
out("\vsta c64.SCRATCH_ZPWORD1+1")
if target.datatype == DataType.BYTE: if target.datatype == DataType.BYTE:
if stmt.operator == "++": out("\vclc" if stmt.operator == "++" else "\vsec")
out("\vjsr il65_lib.incr_deref_byte") out("\vjsr il65_lib.incrdecr_deref_byte_reg_AY")
else:
out("\vjsr il65_lib.decr_deref_byte")
elif target.datatype == DataType.WORD: elif target.datatype == DataType.WORD:
if stmt.operator == "++": out("\vclc" if stmt.operator == "++" else "\vsec")
out("\vjsr il65_lib.incr_deref_word") out("\vjsr il65_lib.incrdecr_deref_word_reg_AY")
else:
out("\vjsr il65_lib.decr_deref_word")
else: else:
raise CodeError("cannot inc/decrement dereferenced literal of type " + str(target.datatype), stmt) raise CodeError("cannot inc/decrement dereferenced literal of type " + str(target.datatype), stmt)
else: 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) raise CodeError("can't dereference just a single register, need combined register", target)
reg = target.operand.name reg = target.operand.name
if stmt.howmuch == 1: if stmt.howmuch == 1:
if stmt.operator == "++": out("\vclc" if stmt.operator == "++" else "\vsec")
out("\vclc")
else:
out("\vsec")
if target.datatype == DataType.BYTE: if target.datatype == DataType.BYTE:
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True): with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
out("\vjsr il65_lib.incrdecr_deref_byte_reg_" + reg) out("\vjsr il65_lib.incrdecr_deref_byte_reg_" + reg)

View File

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