From d18876ee709cc313c639736b03acdd4157998c52 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 3 Feb 2018 01:53:07 +0100 Subject: [PATCH] incrdecr codegen --- il65/emit/__init__.py | 12 +++++++++-- il65/emit/assignment.py | 24 ++++++++++++---------- il65/emit/calls.py | 10 +++++++--- il65/emit/generate.py | 44 +++++++++++++++++++++++------------------ il65/emit/incrdecr.py | 36 ++++++++++++++------------------- todo.ill | 15 +++++++------- 6 files changed, 78 insertions(+), 63 deletions(-) diff --git a/il65/emit/__init__.py b/il65/emit/__init__.py index 0922faf6a..12124fa0e 100644 --- a/il65/emit/__init__.py +++ b/il65/emit/__init__.py @@ -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" diff --git a/il65/emit/assignment.py b/il65/emit/assignment.py index 85b8dd1eb..b37f78058 100644 --- a/il65/emit/assignment.py +++ b/il65/emit/assignment.py @@ -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) diff --git a/il65/emit/calls.py b/il65/emit/calls.py index b188d20f5..4f9529a37 100644 --- a/il65/emit/calls.py +++ b/il65/emit/calls.py @@ -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 diff --git a/il65/emit/generate.py b/il65/emit/generate.py index 6e5da993d..dd40cb0fe 100644 --- a/il65/emit/generate.py +++ b/il65/emit/generate.py @@ -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) diff --git a/il65/emit/incrdecr.py b/il65/emit/incrdecr.py index fc551bb1e..43e8915c4 100644 --- a/il65/emit/incrdecr.py +++ b/il65/emit/incrdecr.py @@ -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) diff --git a/todo.ill b/todo.ill index b7db904de..20c4b9fe1 100644 --- a/todo.ill +++ b/todo.ill @@ -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 }