restructure codegen

This commit is contained in:
Irmen de Jong 2018-03-06 22:25:45 +01:00
parent 44c0d243ef
commit 4d929e00f5
16 changed files with 171 additions and 152 deletions

1
il65/codegen/__init__.py Normal file
View File

@ -0,0 +1 @@
# package

View File

@ -1,21 +1,15 @@
""" """
Programming Language for 6502/6510 microprocessors, codename 'Sick' Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the assembly code generator (from the parse tree) This is the 6502 assembly code generator (directly 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 attr import attr
from typing import Set, Callable, no_type_check from typing import Set, Callable
from ..datatypes import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE from ...plyparse import Scope, AstNode
from ..plyparse import Scope, AstNode from ...compile import Zeropage
from ..compile import Zeropage
class CodeError(Exception):
pass
@attr.s(repr=False, cmp=False) @attr.s(repr=False, cmp=False)
@ -26,56 +20,6 @@ class Context:
floats_enabled = attr.ib(type=bool) floats_enabled = attr.ib(type=bool)
def to_hex(number: int) -> str:
# 0..15 -> "0".."15"
# 16..255 -> "$10".."$ff"
# 256..65536 -> "$0100".."$ffff"
assert type(number) is int
if number is None:
raise ValueError("number")
if 0 <= number < 16:
return str(number)
if 0 <= number < 0x100:
return "${:02x}".format(number)
if 0 <= number < 0x10000:
return "${:04x}".format(number)
raise OverflowError(number)
def to_mflpt5(number: float) -> bytearray:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
number = float(number)
if number < FLOAT_MAX_NEGATIVE or number > FLOAT_MAX_POSITIVE:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
if number == 0.0:
return bytearray([0, 0, 0, 0, 0])
if number < 0.0:
sign = 0x80000000
number = -number
else:
sign = 0x00000000
mant, exp = math.frexp(number)
exp += 128
if exp < 1:
# underflow, use zero instead
return bytearray([0, 0, 0, 0, 0])
if exp > 255:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
mant = sign | int(mant * 0x100000000) & 0x7fffffff
return bytearray([exp]) + int.to_bytes(mant, 4, "big")
def mflpt5_to_float(mflpt: bytearray) -> float:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
if mflpt == bytearray([0, 0, 0, 0, 0]):
return 0.0
exp = mflpt[0] - 128
sign = mflpt[1] & 0x80
number = 0x80000000 | int.from_bytes(mflpt[1:], "big")
number = float(number) * 2**exp / 0x100000000
return -number if sign else number
@contextlib.contextmanager @contextlib.contextmanager
def preserving_registers(registers: Set[str], scope: Scope, out: Callable, loads_a_within: bool=False, force_preserve: bool=False): def preserving_registers(registers: Set[str], scope: Scope, out: Callable, loads_a_within: bool=False, force_preserve: bool=False):
# this sometimes clobbers a ZP scratch register and is therefore NOT safe to use in interrupts # this sometimes clobbers a ZP scratch register and is therefore NOT safe to use in interrupts

View File

@ -6,10 +6,11 @@ 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, Dereference from . import preserving_registers, Context
from . import CodeError, preserving_registers, to_hex, Context from ..shared import CodeError, to_hex
from ..datatypes import REGISTER_BYTES, VarType, DataType from ...plyparse import Scope, Assignment, AugAssignment, Register, LiteralValue, SymbolName, VarDef, Dereference
from ..compile import Zeropage from ...datatypes import REGISTER_BYTES, VarType, DataType
from ...compile import Zeropage
def generate_assignment(ctx: Context) -> None: def generate_assignment(ctx: Context) -> None:

View File

@ -5,8 +5,9 @@ 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 ..plyparse import Goto, SubCall, LiteralValue, SymbolName, Dereference, Register from . import Context
from . import Context, CodeError, to_hex from ..shared import CodeError, to_hex
from ...plyparse import Goto, SubCall, LiteralValue, SymbolName, Dereference
def generate_goto(ctx: Context) -> None: def generate_goto(ctx: Context) -> None:

View File

@ -8,14 +8,15 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
import os import os
import datetime import datetime
from typing import TextIO, Callable, no_type_check from typing import TextIO, Callable, no_type_check
from ..plylex import print_bold from . import Context
from ..plyparse import (Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, ZpOptions,
InlineAssembly, Return, Register, Goto, SubCall, Assignment, AugAssignment, IncrDecr)
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
from .incrdecr import generate_incrdecr from .incrdecr import generate_incrdecr
from ..shared import CodeError, to_hex, to_mflpt5
from ...plylex import print_bold
from ...plyparse import (Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, ZpOptions,
InlineAssembly, Return, Register, Goto, SubCall, Assignment, AugAssignment, IncrDecr)
class Output: class Output:

View File

@ -7,9 +7,10 @@ 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 ..plyparse import VarDef, Register, IncrDecr, SymbolName, Dereference, LiteralValue, scoped_name from . import Context, preserving_registers
from ..datatypes import VarType, DataType, REGISTER_BYTES from ..shared import CodeError, to_hex
from . import CodeError, preserving_registers, to_hex, Context from ...plyparse import VarDef, Register, IncrDecr, SymbolName, Dereference, LiteralValue, scoped_name
from ...datatypes import VarType, DataType, REGISTER_BYTES
def generate_incrdecr(ctx: Context) -> None: def generate_incrdecr(ctx: Context) -> None:

View File

@ -7,9 +7,9 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
from collections import defaultdict from collections import defaultdict
from typing import Dict, List, Callable, Any, no_type_check from typing import Dict, List, Callable, Any, no_type_check
from ..plyparse import Block, VarDef, LiteralValue, AddressOf from ..shared import to_hex, to_mflpt5, CodeError
from ..datatypes import DataType, VarType, STRING_DATATYPES from ...plyparse import Block, VarDef, LiteralValue, AddressOf
from . import to_hex, to_mflpt5, CodeError from ...datatypes import DataType, VarType, STRING_DATATYPES
def generate_block_init(out: Callable, block: Block) -> None: def generate_block_init(out: Callable, block: Block) -> None:

64
il65/codegen/shared.py Normal file
View File

@ -0,0 +1,64 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
Shared logic for the code generators.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import math
from ..datatypes import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE
class CodeError(Exception):
pass
def to_hex(number: int) -> str:
# 0..15 -> "0".."15"
# 16..255 -> "$10".."$ff"
# 256..65536 -> "$0100".."$ffff"
assert type(number) is int
if number is None:
raise ValueError("number")
if 0 <= number < 16:
return str(number)
if 0 <= number < 0x100:
return "${:02x}".format(number)
if 0 <= number < 0x10000:
return "${:04x}".format(number)
raise OverflowError(number)
def to_mflpt5(number: float) -> bytearray:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
number = float(number)
if number < FLOAT_MAX_NEGATIVE or number > FLOAT_MAX_POSITIVE:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
if number == 0.0:
return bytearray([0, 0, 0, 0, 0])
if number < 0.0:
sign = 0x80000000
number = -number
else:
sign = 0x00000000
mant, exp = math.frexp(number)
exp += 128
if exp < 1:
# underflow, use zero instead
return bytearray([0, 0, 0, 0, 0])
if exp > 255:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
mant = sign | int(mant * 0x100000000) & 0x7fffffff
return bytearray([exp]) + int.to_bytes(mant, 4, "big")
def mflpt5_to_float(mflpt: bytearray) -> float:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
if mflpt == bytearray([0, 0, 0, 0, 0]):
return 0.0
exp = mflpt[0] - 128
sign = mflpt[1] & 0x80
number = 0x80000000 | int.from_bytes(mflpt[1:], "big")
number = float(number) * 2**exp / 0x100000000
return -number if sign else number

View File

@ -0,0 +1,8 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the tinyvm stack based program generator
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
# @todo

View File

@ -12,7 +12,7 @@ import argparse
import subprocess import subprocess
from .compile import PlyParser from .compile import PlyParser
from .optimize import optimize from .optimize import optimize
from .emit.generate import AssemblyGenerator from .codegen.mos6502.generate import AssemblyGenerator
from .plylex import print_bold from .plylex import print_bold
from .plyparse import ProgramFormat from .plyparse import ProgramFormat

View File

@ -18,12 +18,13 @@ from .datatypes import (DataType, VarType, REGISTER_SYMBOLS, REGISTER_BYTES, REG
FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE, char_to_bytevalue) FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE, char_to_bytevalue)
__all__ = ["ProgramFormat", "ZpOptions", "math_functions", "builtin_functions", "ParseError", "ExpressionEvaluationError", __all__ = ["ProgramFormat", "ZpOptions", "math_functions", "builtin_functions",
"UndefinedSymbolError", "AstNode", "Directive", "Scope", "Block", "Module", "Label", "Expression", "AstNode", "Directive", "Scope", "Block", "Module", "Label", "Expression",
"Register", "Subroutine", "LiteralValue", "AddressOf", "SymbolName", "Dereference", "IncrDecr", "Register", "Subroutine", "LiteralValue", "AddressOf", "SymbolName", "Dereference", "IncrDecr",
"ExpressionWithOperator", "Goto", "SubCall", "VarDef", "Return", "Assignment", "AugAssignment", "ExpressionWithOperator", "Goto", "SubCall", "VarDef", "Return", "Assignment", "AugAssignment",
"InlineAssembly", "BuiltinFunction", "TokenFilter", "parser", "connect_parents", "DatatypeNode", "InlineAssembly", "BuiltinFunction", "TokenFilter", "parser", "connect_parents", "DatatypeNode",
"parse_file", "coerce_constant_value", "datatype_of", "check_symbol_definition", "NotCompiletimeConstantError"] "parse_file", "coerce_constant_value", "datatype_of", "check_symbol_definition", "scoped_name",
"NotCompiletimeConstantError", "ExpressionEvaluationError", "ParseError", "UndefinedSymbolError"]
class ProgramFormat(enum.Enum): class ProgramFormat(enum.Enum):

View File

@ -0,0 +1 @@
# package

View File

@ -0,0 +1,60 @@
import pytest
from il65.datatypes import FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE
from il65.codegen.shared import to_hex, to_mflpt5
def test_to_hex():
assert to_hex(0) == "0"
assert to_hex(1) == "1"
assert to_hex(10) == "10"
assert to_hex(15) == "15"
assert to_hex(16) == "$10"
assert to_hex(255) == "$ff"
assert to_hex(256) == "$0100"
assert to_hex(20060) == "$4e5c"
assert to_hex(65535) == "$ffff"
with pytest.raises(OverflowError):
to_hex(-1)
with pytest.raises(OverflowError):
to_hex(65536)
def test_float_to_mflpt5():
mflpt = to_mflpt5(1.0)
assert type(mflpt) is bytearray
assert b"\x00\x00\x00\x00\x00" == to_mflpt5(0)
assert b"\x82\x49\x0F\xDA\xA1" == to_mflpt5(3.141592653)
assert b"\x82\x49\x0F\xDA\xA2" == to_mflpt5(3.141592653589793)
assert b"\x90\x80\x00\x00\x00" == to_mflpt5(-32768)
assert b"\x81\x00\x00\x00\x00" == to_mflpt5(1)
assert b"\x80\x35\x04\xF3\x34" == to_mflpt5(0.7071067812)
assert b"\x80\x35\x04\xF3\x33" == to_mflpt5(0.7071067811865476)
assert b"\x81\x35\x04\xF3\x34" == to_mflpt5(1.4142135624)
assert b"\x81\x35\x04\xF3\x33" == to_mflpt5(1.4142135623730951)
assert b"\x80\x80\x00\x00\x00" == to_mflpt5(-.5)
assert b"\x80\x31\x72\x17\xF8" == to_mflpt5(0.69314718061)
assert b"\x80\x31\x72\x17\xF7" == to_mflpt5(0.6931471805599453)
assert b"\x84\x20\x00\x00\x00" == to_mflpt5(10)
assert b"\x9E\x6E\x6B\x28\x00" == to_mflpt5(1000000000)
assert b"\x80\x00\x00\x00\x00" == to_mflpt5(.5)
assert b"\x81\x38\xAA\x3B\x29" == to_mflpt5(1.4426950408889634)
assert b"\x81\x49\x0F\xDA\xA2" == to_mflpt5(1.5707963267948966)
assert b"\x83\x49\x0F\xDA\xA2" == to_mflpt5(6.283185307179586)
assert b"\x7F\x00\x00\x00\x00" == to_mflpt5(.25)
def test_float_range():
assert b"\xff\x7f\xff\xff\xff" == to_mflpt5(FLOAT_MAX_POSITIVE)
assert b"\xff\xff\xff\xff\xff" == to_mflpt5(FLOAT_MAX_NEGATIVE)
with pytest.raises(OverflowError):
to_mflpt5(1.7014118346e+38)
with pytest.raises(OverflowError):
to_mflpt5(-1.7014118346e+38)
with pytest.raises(OverflowError):
to_mflpt5(1.7014118347e+38)
with pytest.raises(OverflowError):
to_mflpt5(-1.7014118347e+38)
assert b"\x03\x39\x1d\x15\x63" == to_mflpt5(1.7e-38)
assert b"\x00\x00\x00\x00\x00" == to_mflpt5(1.7e-39)
assert b"\x03\xb9\x1d\x15\x63" == to_mflpt5(-1.7e-38)
assert b"\x00\x00\x00\x00\x00" == to_mflpt5(-1.7e-39)

View File

@ -1,9 +1,8 @@
import pytest import pytest
from il65.datatypes import DataType, STRING_DATATYPES, FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE, char_to_bytevalue from il65.datatypes import DataType, STRING_DATATYPES, char_to_bytevalue
from il65.plyparse import coerce_constant_value, LiteralValue, Scope, SymbolName, VarDef from il65.plyparse import coerce_constant_value, LiteralValue, Scope, SymbolName, VarDef
from il65.compile import ParseError from il65.compile import ParseError
from il65.plylex import SourceRef from il65.plylex import SourceRef
from il65.emit import to_hex, to_mflpt5
def test_datatypes(): def test_datatypes():
@ -31,63 +30,6 @@ def test_parseerror():
assert str(p) == "filename:99:42 message" assert str(p) == "filename:99:42 message"
def test_to_hex():
assert to_hex(0) == "0"
assert to_hex(1) == "1"
assert to_hex(10) == "10"
assert to_hex(15) == "15"
assert to_hex(16) == "$10"
assert to_hex(255) == "$ff"
assert to_hex(256) == "$0100"
assert to_hex(20060) == "$4e5c"
assert to_hex(65535) == "$ffff"
with pytest.raises(OverflowError):
to_hex(-1)
with pytest.raises(OverflowError):
to_hex(65536)
def test_float_to_mflpt5():
mflpt = to_mflpt5(1.0)
assert type(mflpt) is bytearray
assert b"\x00\x00\x00\x00\x00" == to_mflpt5(0)
assert b"\x82\x49\x0F\xDA\xA1" == to_mflpt5(3.141592653)
assert b"\x82\x49\x0F\xDA\xA2" == to_mflpt5(3.141592653589793)
assert b"\x90\x80\x00\x00\x00" == to_mflpt5(-32768)
assert b"\x81\x00\x00\x00\x00" == to_mflpt5(1)
assert b"\x80\x35\x04\xF3\x34" == to_mflpt5(0.7071067812)
assert b"\x80\x35\x04\xF3\x33" == to_mflpt5(0.7071067811865476)
assert b"\x81\x35\x04\xF3\x34" == to_mflpt5(1.4142135624)
assert b"\x81\x35\x04\xF3\x33" == to_mflpt5(1.4142135623730951)
assert b"\x80\x80\x00\x00\x00" == to_mflpt5(-.5)
assert b"\x80\x31\x72\x17\xF8" == to_mflpt5(0.69314718061)
assert b"\x80\x31\x72\x17\xF7" == to_mflpt5(0.6931471805599453)
assert b"\x84\x20\x00\x00\x00" == to_mflpt5(10)
assert b"\x9E\x6E\x6B\x28\x00" == to_mflpt5(1000000000)
assert b"\x80\x00\x00\x00\x00" == to_mflpt5(.5)
assert b"\x81\x38\xAA\x3B\x29" == to_mflpt5(1.4426950408889634)
assert b"\x81\x49\x0F\xDA\xA2" == to_mflpt5(1.5707963267948966)
assert b"\x83\x49\x0F\xDA\xA2" == to_mflpt5(6.283185307179586)
assert b"\x7F\x00\x00\x00\x00" == to_mflpt5(.25)
def test_float_range():
assert b"\xff\x7f\xff\xff\xff" == to_mflpt5(FLOAT_MAX_POSITIVE)
assert b"\xff\xff\xff\xff\xff" == to_mflpt5(FLOAT_MAX_NEGATIVE)
with pytest.raises(OverflowError):
to_mflpt5(1.7014118346e+38)
with pytest.raises(OverflowError):
to_mflpt5(-1.7014118346e+38)
with pytest.raises(OverflowError):
to_mflpt5(1.7014118347e+38)
with pytest.raises(OverflowError):
to_mflpt5(-1.7014118347e+38)
assert b"\x03\x39\x1d\x15\x63" == to_mflpt5(1.7e-38)
assert b"\x00\x00\x00\x00\x00" == to_mflpt5(1.7e-39)
assert b"\x03\xb9\x1d\x15\x63" == to_mflpt5(-1.7e-38)
assert b"\x00\x00\x00\x00\x00" == to_mflpt5(-1.7e-39)
def test_char_to_bytevalue(): def test_char_to_bytevalue():
assert char_to_bytevalue('a') == 65 assert char_to_bytevalue('a') == 65
assert char_to_bytevalue('\n') == 13 assert char_to_bytevalue('\n') == 13

View File

@ -8,7 +8,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
import enum import enum
import struct import struct
from typing import Callable from typing import Callable
from il65.emit import mflpt5_to_float, to_mflpt5 from il65.codegen.shared import mflpt5_to_float, to_mflpt5
class DataType(enum.IntEnum): class DataType(enum.IntEnum):

View File

@ -23,14 +23,12 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
# or in one of the dynamic variables. # or in one of the dynamic variables.
# #
# I/O: either via programmed I/O routines: # I/O: either via programmed I/O routines:
# write [byte/bytearray to text output/screen], # write [byte/bytearray to text output/screen] : syscall_printstr / syscall_printchr,
# read [byte/bytearray from keyboard], # read [byte/bytearray from keyboard] : syscall_input / syscall_getchr (both blocking)
# wait [till any input comes available], @todo
# check [if input is available) @todo
# or via memory-mapped I/O (text screen matrix, keyboard scan register) # or via memory-mapped I/O (text screen matrix, keyboard scan register)
# #
# CPU: stack based execution, no registers. # CPU: single threaded, stack based execution,
# unlimited dynamic variables (v0, v1, ...) that have a value and a type. # no registers, but unlimited dynamic variables (v0, v1, ...) that have a value and a type.
# types: # types:
# 1-bit boolean, # 1-bit boolean,
# 8-bit byte (singed and unsigned), # 8-bit byte (singed and unsigned),
@ -41,9 +39,6 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
# matrix (2-dimensional array) of bytes (signed and unsigned). # matrix (2-dimensional array) of bytes (signed and unsigned).
# all of these can have the flag CONST as well which means they cannot be modified. # all of these can have the flag CONST as well which means they cannot be modified.
# #
# push (constant,
# mark, unwind to previous mark.
#
# CPU INSTRUCTIONS: # CPU INSTRUCTIONS:
# stack manipulation mainly: # stack manipulation mainly:
# nop # nop
@ -53,12 +48,11 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
# jump label # jump label
# jump_if_true label, jump_if_false label # jump_if_true label, jump_if_false label
# jump_if_status_XX label special system dependent status register conditional check such as carry bit or overflow bit) # jump_if_status_XX label special system dependent status register conditional check such as carry bit or overflow bit)
# call function (arguments are on stack)
# return (return values on stack) # return (return values on stack)
# syscall function (special system dependent implementation) # syscall function (special system dependent implementation)
# call function (arguments are on stack)
# enter / exit (function call frame)
# #
# TIMER INTERRUPT: triggered around each 1/60th of a second. # TIMER 'INTERRUPT': triggered around each 1/60th of a second.
# executes on a DIFFERENT stack and with a different PROGRAM LIST, # executes on a DIFFERENT stack and with a different PROGRAM LIST,
# but with access to ALL THE SAME DYNAMIC VARIABLES. # but with access to ALL THE SAME DYNAMIC VARIABLES.
# This suspends the main program until the timer program RETURNs! # This suspends the main program until the timer program RETURNs!