Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/unify bool #914

Merged
merged 13 commits into from
Nov 27, 2024
25 changes: 22 additions & 3 deletions src/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import os
import sys
from collections.abc import Callable
from enum import Enum
from enum import StrEnum

from src.api import errmsg, global_, options, python_version_check
from src.api.options import ANYTYPE, Action
Expand All @@ -24,14 +24,21 @@
# Common setup and configuration for all tools
# ------------------------------------------------------
@enum.unique
class ConfigSections(str, Enum):
class ConfigSections(StrEnum):
ZXBC = "zxbc"
ZXBASM = "zxbasm"
ZXBPP = "zxbpp"


@enum.unique
class OPTION(str, Enum):
class OptimizationStrategy(StrEnum):
Size = "size"
Speed = "speed"
Auto = "auto"


@enum.unique
class OPTION(StrEnum):
OUTPUT_FILENAME = "output_filename"
INPUT_FILENAME = "input_filename"
STDERR_FILENAME = "stderr_filename"
Expand Down Expand Up @@ -76,6 +83,9 @@ class OPTION(str, Enum):
ASM_ZXNEXT = "zxnext"
FORCE_ASM_BRACKET = "force_asm_brackets"

# Optimization Preferences
OPT_STRATEGY = "opt_strategy"


OPTIONS = options.Options()
OPTIONS_NOT_SAVED = {
Expand Down Expand Up @@ -226,6 +236,15 @@ def init() -> None:
# Whether to show WXXX warning codes or not
OPTIONS(Action.ADD, name=OPTION.HIDE_WARNING_CODES, type=bool, default=False, ignore_none=True)

# Optimization preferences
OPTIONS(
Action.ADD,
name=OPTION.OPT_STRATEGY,
type=OptimizationStrategy,
default=OptimizationStrategy.Auto,
ignore_none=True,
)

OPTIONS(
Action.ADD,
name=OPTION.PROJECT_FILENAME,
Expand Down
2 changes: 1 addition & 1 deletion src/api/errmsg.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def warning_command_line_flag_deprecation(flag: str) -> None:
"""Warning signaling command line flag is deprecated.
This is a special warning that can't be silenced, and needs no line number nor filename.
"""
# msg_output(f"WARNING: deprecated flag {flag}") # TODO: To be enabled upon 1.18+
msg_output(f"WARNING: deprecated flag {flag}") # TODO: To be enabled upon 1.18+


# region [Warnings]
Expand Down
9 changes: 5 additions & 4 deletions src/api/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@

import enum
import json
from typing import Any
from enum import StrEnum
from typing import Any, Final

from src.api.exception import Error

__all__ = ["Option", "Options", "ANYTYPE", "Action"]
__all__: Final[tuple[str, ...]] = "Option", "Options", "ANYTYPE", "Action"


class ANYTYPE:
Expand Down Expand Up @@ -51,7 +52,7 @@ def __str__(self):
return "Cannot pop option '%s'. Option stack is empty" % self.option


class InvalidValueError(Error):
class InvalidValueError(ValueError, Error):
def __init__(self, option_name, _type, value):
self.option = option_name
self.value = value
Expand Down Expand Up @@ -159,7 +160,7 @@ def pop(self) -> Any:
# Options commands
# ----------------------------------------------------------------------
@enum.unique
class Action(str, enum.Enum):
class Action(StrEnum):
ADD = "add"
ADD_IF_NOT_DEFINED = "add_if_not_defined"
CLEAR = "clear"
Expand Down
3 changes: 3 additions & 0 deletions src/api/symboltable/symboltable.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ def access_id(
# The entry was already declared. If it's type is auto and the default type is not None,
# update its type.
if default_type is not None and result.type_ == self.basic_types[TYPE.unknown]:
if default_type == self.basic_types[TYPE.boolean]:
default_type = self.basic_types[TYPE.ubyte]

result.type_ = default_type
warning_implicit_type(lineno, id_, default_type.name)

Expand Down
3 changes: 1 addition & 2 deletions src/arch/z80/backend/_8bit.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,14 +745,13 @@ def and8(cls, ins: Quad) -> list[str]:
return output

output = cls.get_oper(op1, op2)
# output.append('call __AND8')

lbl = tmp_label()
output.append("or a")
output.append("jr z, %s" % lbl)
output.append("ld a, h")
output.append("%s:" % lbl)
output.append("push af")
# REQUIRES.add('and8.asm')

return output

Expand Down
99 changes: 88 additions & 11 deletions src/arch/z80/backend/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Final

from src.api import global_, tmp_labels
from src.api.config import OPTIONS, OptimizationStrategy
from src.api.exception import TempAlreadyFreedError

from .runtime import LABEL_REQUIRED_MODULES, NAMESPACE, RUNTIME_LABELS
Expand Down Expand Up @@ -296,12 +297,52 @@ def get_bytes_size(elements: list[str]) -> int:
return len(get_bytes(elements))


def to_bool(stype: DataType) -> list[str]:
"""Returns the instruction sequence for converting the number given number (in the stack)
to boolean (just 0 (False) or non-zero (True))."""

if stype in (U8_t, I8_t):
return []

if stype in (U16_t, I16_t):
return [
"ld a, h" "or l",
]

if stype in (U32_t, I32_t, F16_t):
return ["ld a, h" "or l" "or d", "or e,"]

if stype == F_t:
return [
"or b",
"or c",
"or d",
"or e",
]

raise NotImplementedError(f"type conversion from {stype} to bool is undefined")


def normalize_boolean() -> list[str]:
if OPTIONS.opt_strategy == OptimizationStrategy.Size:
return [runtime_call(RuntimeLabel.NORMALIZE_BOOLEAN)]

return [
"sub 1", # Carry if A = 0
"sbc a, a", # 0xFF if A was 0, 0 otherwise
"inc a", # 0 if A was 0, 1 otherwise
]


def to_byte(stype: DataType) -> list[str]:
"""Returns the instruction sequence for converting from
the given type to byte.
"""
output = []

if stype == BOOL_t:
return normalize_boolean()

if stype in (I8_t, U8_t):
return []

Expand All @@ -322,7 +363,10 @@ def to_word(stype: DataType) -> list[str]:
"""
output = [] # List of instructions

if stype == U8_t: # Byte to word
if stype == BOOL_t:
output.extend(normalize_boolean())

if stype in (BOOL_t, U8_t): # Byte to word
output.append("ld l, a")
output.append("ld h, 0")

Expand All @@ -347,25 +391,39 @@ def to_long(stype: DataType) -> list[str]:
"""
output = [] # List of instructions

if stype == BOOL_t:
output = normalize_boolean()
output.extend(
[
"ld l, a",
"ld h, 0",
"ld e, h",
"ld d, h",
]
)
return output

if stype in {I8_t, U8_t, F16_t}: # Byte to word
output = to_word(stype)

if stype != F16_t: # If it's a byte, just copy H to D,E
output.append("ld e, h")
output.append("ld d, h")

if stype in (I16_t, F16_t): # Signed byte or fixed to word
elif stype in (I16_t, F16_t): # Signed byte or fixed to word
output.append("ld a, h")
output.append("add a, a")
output.append("sbc a, a")
output.append("ld e, a")
output.append("ld d, a")

elif stype == "u16":
elif stype == U16_t:
output.append("ld de, 0")

elif stype == F_t:
output.append(runtime_call(RuntimeLabel.FTOU32REG))
else:
raise NotImplementedError(f"type conversion from {stype} to long is undefined")

return output

Expand All @@ -374,16 +432,30 @@ def to_fixed(stype: DataType) -> list[str]:
"""Returns the instruction sequence for converting the given
type stored in DE,HL to fixed DE,HL.
"""
output = [] # List of instructions
if stype == BOOL_t:
output = to_word(stype)
output.extend(
[
"ex de, hl",
"ld hl, 0", # 'Truncate' the fixed point
]
)
return output

if is_int_type(stype):
output = to_word(stype)
output.append("ex de, hl")
output.append("ld hl, 0") # 'Truncate' the fixed point
elif stype == F_t:
output.append(runtime_call(RuntimeLabel.FTOF16REG))
output.extend(
[
"ex de, hl",
"ld hl, 0", # 'Truncate' the fixed point
]
)
return output

return output
if stype == F_t:
return [runtime_call(RuntimeLabel.FTOF16REG)]

raise NotImplementedError(f"type conversion from {stype} to fixed")


def to_float(stype: DataType) -> list[str]:
Expand All @@ -399,17 +471,22 @@ def to_float(stype: DataType) -> list[str]:
output.append(runtime_call(RuntimeLabel.F16TOFREG))
return output

if stype == BOOL_t:
output.extend(normalize_boolean())

# If we reach this point, it's an integer type
if stype == U8_t:
if stype in (BOOL_t, U8_t): # The ZX Spectrum ROM FP-Calc already returns 0 or 1 for Booleans
output.append(runtime_call(RuntimeLabel.U8TOFREG))
elif stype == I8_t:
output.append(runtime_call(RuntimeLabel.I8TOFREG))
else:
elif stype in {I16_t, I32_t, U16_t, U32_t}:
output = to_long(stype)
if stype in (I16_t, I32_t):
output.append(runtime_call(RuntimeLabel.I32TOFREG))
else:
output.append(runtime_call(RuntimeLabel.U32TOFREG))
else:
raise NotImplementedError(f"type conversion from {stype} to float is undefined")

return output

Expand Down
7 changes: 6 additions & 1 deletion src/arch/z80/backend/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
get_bytes_size,
new_ASMID,
runtime_call,
to_bool,
to_byte,
to_fixed,
to_float,
Expand Down Expand Up @@ -341,7 +342,7 @@ def _cast(ins: Quad):
xsB = sB = YY_TYPES[tB] # Type sizes

output = []
if tA in ("u8", "i8"):
if tA in ("u8", "i8", "bool"):
output.extend(Bits8.get_oper(ins[4]))
elif tA in ("u16", "i16"):
output.extend(Bits16.get_oper(ins[4]))
Expand All @@ -364,6 +365,10 @@ def _cast(ins: Quad):
output.extend(to_fixed(tA))
elif tB == "f":
output.extend(to_float(tA))
elif tB == "bool":
output.extend(to_bool(tA))
else:
raise exception.GenericError("Internal error: invalid typecast from %s to %s" % (tA, tB))

xsB += sB % 2 # make it even (round up)

Expand Down
15 changes: 0 additions & 15 deletions src/arch/z80/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@
# String comparison functions
# String arithmetic functions
from ._str import String
from .common import runtime_call
from .generic import (
_call,
_cast,
Expand Down Expand Up @@ -130,7 +129,6 @@
from .icinstruction import ICInstruction
from .quad import Quad
from .runtime import NAMESPACE
from .runtime import Labels as RuntimeLabel

__all__ = ("Backend",)

Expand Down Expand Up @@ -677,16 +675,6 @@ def emit_prologue() -> list[str]:

return output

@staticmethod
def emit_cast_to_bool():
"""Convert a byte value to boolean (0 or 1) if
the global flag strictBool is True
"""
if not OPTIONS.strict_bool:
return []

return ["pop af", runtime_call(RuntimeLabel.NORMALIZE_BOOLEAN), "push af"]

@staticmethod
def emit_epilogue() -> list[str]:
"""This special ending autoinitializes required inits
Expand Down Expand Up @@ -780,9 +768,6 @@ def emit(self, *, optimize: bool = True) -> list[str]:
output: list[str] = []
for quad in self.MEMORY:
self._output_join(output, self._QUAD_TABLE[quad.instr].func(quad), optimize=optimize)
# If it is a boolean operation convert it to 0/1 if the STRICT_BOOL flag is True
if common.RE_BOOL.match(quad.instr):
self._output_join(output, self.emit_cast_to_bool(), optimize=optimize)

if optimize and OPTIONS.optimization_level > 1:
self.remove_unused_labels(output)
Expand Down
Loading