|
3 | 3 | # vim ts=4:et:sw=4:ai |
4 | 4 |
|
5 | 5 | import math |
| 6 | +import re |
6 | 7 |
|
7 | 8 | from typing import List |
8 | 9 | from typing import Set |
9 | 10 |
|
10 | 11 | import src.api.global_ as gl |
11 | 12 | import src.api.errors |
12 | | - |
13 | | -from .runtime import RUNTIME_LABELS |
14 | | -from .runtime import LABEL_REQUIRED_MODULES |
15 | | - |
| 13 | +import src.arch |
| 14 | +from src.api import global_ |
| 15 | + |
| 16 | +from src.arch.z80.backend import errors |
| 17 | +from src.arch.z80.backend.errors import InvalidICError as InvalidIC |
| 18 | +from src.arch.z80.backend.runtime import RUNTIME_LABELS, LABEL_REQUIRED_MODULES, NAMESPACE, Labels as RuntimeLabel |
| 19 | + |
| 20 | + |
| 21 | +# List of modules (in alphabetical order) that, if included, should call MEM_INIT |
| 22 | +MEMINITS = { |
| 23 | + "alloc.asm", |
| 24 | + "loadstr.asm", |
| 25 | + "storestr2.asm", |
| 26 | + "storestr.asm", |
| 27 | + "strcat.asm", |
| 28 | + "strcpy.asm", |
| 29 | + "string.asm", |
| 30 | + "strslice.asm", |
| 31 | +} |
| 32 | + |
| 33 | +# Internal data types definition, with its size in bytes, or -1 if it is variable (string) |
| 34 | +# Compound types are only arrays, and have the t |
| 35 | +YY_TYPES = { |
| 36 | + "u8": 1, # 8 bit unsigned integer |
| 37 | + "u16": 2, # 16 bit unsigned integer |
| 38 | + "u32": 4, # 32 bit unsigned integer |
| 39 | + "i8": 1, # 8 bit SIGNED integer |
| 40 | + "i16": 2, # 16 bit SIGNED integer |
| 41 | + "i32": 4, # 32 bit SIGNED integer |
| 42 | + "f16": 4, # -32768.9999 to 32767.9999 -aprox.- fixed point decimal (step = 1/2^16) |
| 43 | + "f": 5, # Floating point |
| 44 | +} |
| 45 | + |
| 46 | +# Matches a boolean instruction like 'equ16' or 'andi32' |
| 47 | +RE_BOOL = re.compile(r"^(eq|ne|lt|le|gt|ge|and|or|xor|not)(([ui](8|16|32))|(f16|f|str))$") |
| 48 | + |
| 49 | +# Marches an hexadecimal number |
| 50 | +RE_HEXA = re.compile(r"^[0-9A-F]+$") |
| 51 | + |
| 52 | +# (ix +/- ...) regexp |
| 53 | +RE_IX_IDX = re.compile(r"^\([ \t]*ix[ \t]*[-+][ \t]*.+\)$") |
| 54 | + |
| 55 | +# Label for the program START end EXIT |
| 56 | +START_LABEL = f"{NAMESPACE}.__START_PROGRAM" |
| 57 | +END_LABEL = f"{NAMESPACE}.__END_PROGRAM" |
| 58 | + |
| 59 | +CALL_BACK = f"{NAMESPACE}.__CALL_BACK__" |
| 60 | +MAIN_LABEL = f"{NAMESPACE}.__MAIN_PROGRAM__" |
| 61 | +DATA_LABEL = global_.ZXBASIC_USER_DATA |
| 62 | +DATA_END_LABEL = f"{DATA_LABEL}_END" |
| 63 | + |
| 64 | +# ------------------------------------------------------- |
| 65 | +# Runtime flags. Must be initialized on each compilation |
| 66 | +# ------------------------------------------------------- |
| 67 | + |
| 68 | +# Whether the FunctionExit scheme has been already used or not |
| 69 | +FLAG_use_function_exit = False |
| 70 | + |
| 71 | +# Whether an 'end' has already been emitted or not |
| 72 | +FLAG_end_emitted = False |
| 73 | + |
| 74 | +# This will be appended at the end of code emission (useful for lvard, for example) |
| 75 | +AT_END = [] |
| 76 | + |
| 77 | +# A table with ASM block entered by the USER (these won't be optimized) |
| 78 | +ASMS = {} |
| 79 | +ASMCOUNT = 0 # ASM blocks counter |
16 | 80 |
|
17 | 81 | MEMORY = [] # Must be initialized by with init() |
18 | 82 |
|
|
35 | 99 | # GENERATED labels __LABELXX |
36 | 100 | TMP_LABELS: Set[str] = set() |
37 | 101 |
|
| 102 | +# ------------------------------------ |
| 103 | +# Table describing operations |
| 104 | +# 'OPERATOR' -> [Number of arguments] |
| 105 | +# ------------------------------------ |
| 106 | +QUADS = {} |
38 | 107 |
|
39 | | -def init(): |
40 | | - global LABEL_COUNTER |
41 | | - global TMP_COUNTER |
42 | | - |
43 | | - LABEL_COUNTER = 0 |
44 | | - TMP_COUNTER = 0 |
45 | 108 |
|
46 | | - del MEMORY[:] |
47 | | - del TMP_STORAGES[:] |
48 | | - REQUIRES.clear() |
49 | | - INITS.clear() |
50 | | - TMP_LABELS.clear() |
| 109 | +# ------------------------------------ |
| 110 | +# Shared functions |
| 111 | +# ------------------------------------ |
51 | 112 |
|
52 | 113 |
|
53 | 114 | def log2(x: float) -> float: |
@@ -170,3 +231,225 @@ def _f_ops(op1, op2, swap=True): |
170 | 231 | def is_int_type(stype: str) -> bool: |
171 | 232 | """Returns whether a given type is integer""" |
172 | 233 | return stype[0] in ("u", "i") |
| 234 | + |
| 235 | + |
| 236 | +class Quad: |
| 237 | + """Implements a Quad code instruction.""" |
| 238 | + |
| 239 | + def __init__(self, *args): |
| 240 | + """Creates a quad-uple checking it has the current params. |
| 241 | + Operators should be passed as Quad('+', tSymbol, val1, val2) |
| 242 | + """ |
| 243 | + if not args: |
| 244 | + raise InvalidIC("<null>") |
| 245 | + |
| 246 | + if args[0] not in QUADS.keys(): |
| 247 | + errors.throw_invalid_quad_code(args[0]) |
| 248 | + |
| 249 | + if len(args) - 1 != QUADS[args[0]][0]: |
| 250 | + errors.throw_invalid_quad_params(args[0], len(args) - 1, QUADS[args[0]][0]) |
| 251 | + |
| 252 | + args = tuple([str(x) for x in args]) # Convert it to strings |
| 253 | + |
| 254 | + self.quad = args |
| 255 | + self.op = args[0] |
| 256 | + |
| 257 | + def __str__(self): |
| 258 | + """String representation""" |
| 259 | + return str(self.quad) |
| 260 | + |
| 261 | + |
| 262 | +def init(): |
| 263 | + global LABEL_COUNTER |
| 264 | + global TMP_COUNTER |
| 265 | + global ASMCOUNT |
| 266 | + global FLAG_end_emitted |
| 267 | + global FLAG_use_function_exit |
| 268 | + |
| 269 | + LABEL_COUNTER = 0 |
| 270 | + TMP_COUNTER = 0 |
| 271 | + ASMCOUNT = 0 |
| 272 | + |
| 273 | + MEMORY.clear() |
| 274 | + TMP_STORAGES.clear() |
| 275 | + REQUIRES.clear() |
| 276 | + INITS.clear() |
| 277 | + TMP_LABELS.clear() |
| 278 | + ASMS.clear() |
| 279 | + AT_END.clear() |
| 280 | + |
| 281 | + FLAG_use_function_exit = False |
| 282 | + FLAG_end_emitted = False |
| 283 | + |
| 284 | + |
| 285 | +# ------------------------------------------------------------------ |
| 286 | +# Typecast conversions |
| 287 | +# ------------------------------------------------------------------ |
| 288 | + |
| 289 | + |
| 290 | +def get_bytes(elements: List[str]) -> List[str]: |
| 291 | + """Returns a list a default set of bytes/words in hexadecimal |
| 292 | + (starting with an hex number) or literals (starting with #). |
| 293 | + Numeric values with more than 2 digits represents a WORD (2 bytes) value. |
| 294 | + E.g. '01' => 01h, '001' => 1, 0 bytes (0001h) |
| 295 | + Literal values starts with # (1 byte) or ## (2 bytes) |
| 296 | + E.g. '#label + 1' => (label + 1) & 0xFF |
| 297 | + '##(label + 1)' => (label + 1) & 0xFFFF |
| 298 | + """ |
| 299 | + output = [] |
| 300 | + |
| 301 | + for x in elements: |
| 302 | + if x.startswith("##"): # 2-byte literal |
| 303 | + output.append("({}) & 0xFF".format(x[2:])) |
| 304 | + output.append("(({}) >> 8) & 0xFF".format(x[2:])) |
| 305 | + continue |
| 306 | + |
| 307 | + if x.startswith("#"): # 1-byte literal |
| 308 | + output.append("({}) & 0xFF".format(x[1:])) |
| 309 | + continue |
| 310 | + |
| 311 | + # must be an hex number |
| 312 | + assert RE_HEXA.match(x), 'expected an hex number, got "%s"' % x |
| 313 | + output.append("%02X" % int(x[-2:], 16)) |
| 314 | + if len(x) > 2: |
| 315 | + output.append("%02X" % int(x[-4:-2:], 16)) |
| 316 | + |
| 317 | + return output |
| 318 | + |
| 319 | + |
| 320 | +def get_bytes_size(elements: List[str]) -> int: |
| 321 | + """Defines a memory space with a default set of bytes/words in hexadecimal |
| 322 | + (starting with an hex number) or literals (starting with #). |
| 323 | + Numeric values with more than 2 digits represents a WORD (2 bytes) value. |
| 324 | + E.g. '01' => 01h, '001' => 1, 0 bytes (0001h) |
| 325 | + Literal values starts with # (1 byte) or ## (2 bytes) |
| 326 | + E.g. '#label + 1' => (label + 1) & 0xFF |
| 327 | + '##(label + 1)' => (label + 1) & 0xFFFF |
| 328 | + """ |
| 329 | + return len(get_bytes(elements)) |
| 330 | + |
| 331 | + |
| 332 | +def to_byte(stype): |
| 333 | + """Returns the instruction sequence for converting from |
| 334 | + the given type to byte. |
| 335 | + """ |
| 336 | + output = [] |
| 337 | + |
| 338 | + if stype in ("i8", "u8"): |
| 339 | + return [] |
| 340 | + |
| 341 | + if is_int_type(stype): |
| 342 | + output.append("ld a, l") |
| 343 | + elif stype == "f16": |
| 344 | + output.append("ld a, e") |
| 345 | + elif stype == "f": # Converts C ED LH to byte |
| 346 | + output.append(runtime_call(RuntimeLabel.FTOU32REG)) |
| 347 | + output.append("ld a, l") |
| 348 | + |
| 349 | + return output |
| 350 | + |
| 351 | + |
| 352 | +def to_word(stype): |
| 353 | + """Returns the instruction sequence for converting the given |
| 354 | + type stored in DE,HL to word (unsigned) HL. |
| 355 | + """ |
| 356 | + output = [] # List of instructions |
| 357 | + |
| 358 | + if stype == "u8": # Byte to word |
| 359 | + output.append("ld l, a") |
| 360 | + output.append("ld h, 0") |
| 361 | + |
| 362 | + elif stype == "i8": # Signed byte to word |
| 363 | + output.append("ld l, a") |
| 364 | + output.append("add a, a") |
| 365 | + output.append("sbc a, a") |
| 366 | + output.append("ld h, a") |
| 367 | + |
| 368 | + elif stype == "f16": # Must MOVE HL into DE |
| 369 | + output.append("ex de, hl") |
| 370 | + |
| 371 | + elif stype == "f": |
| 372 | + output.append(runtime_call(RuntimeLabel.FTOU32REG)) |
| 373 | + |
| 374 | + return output |
| 375 | + |
| 376 | + |
| 377 | +def to_long(stype): |
| 378 | + """Returns the instruction sequence for converting the given |
| 379 | + type stored in DE,HL to long (DE, HL). |
| 380 | + """ |
| 381 | + output = [] # List of instructions |
| 382 | + |
| 383 | + if stype in ("i8", "u8", "f16"): # Byte to word |
| 384 | + output = to_word(stype) |
| 385 | + |
| 386 | + if stype != "f16": # If its a byte, just copy H to D,E |
| 387 | + output.append("ld e, h") |
| 388 | + output.append("ld d, h") |
| 389 | + |
| 390 | + if stype in ("i16", "f16"): # Signed byte or fixed to word |
| 391 | + output.append("ld a, h") |
| 392 | + output.append("add a, a") |
| 393 | + output.append("sbc a, a") |
| 394 | + output.append("ld e, a") |
| 395 | + output.append("ld d, a") |
| 396 | + |
| 397 | + elif stype == "u16": |
| 398 | + output.append("ld de, 0") |
| 399 | + |
| 400 | + elif stype == "f": |
| 401 | + output.append(runtime_call(RuntimeLabel.FTOU32REG)) |
| 402 | + |
| 403 | + return output |
| 404 | + |
| 405 | + |
| 406 | +def to_fixed(stype): |
| 407 | + """Returns the instruction sequence for converting the given |
| 408 | + type stored in DE,HL to fixed DE,HL. |
| 409 | + """ |
| 410 | + output = [] # List of instructions |
| 411 | + |
| 412 | + if is_int_type(stype): |
| 413 | + output = to_word(stype) |
| 414 | + output.append("ex de, hl") |
| 415 | + output.append("ld hl, 0") # 'Truncate' the fixed point |
| 416 | + elif stype == "f": |
| 417 | + output.append(runtime_call(RuntimeLabel.FTOF16REG)) |
| 418 | + |
| 419 | + return output |
| 420 | + |
| 421 | + |
| 422 | +def to_float(stype: str) -> List[str]: |
| 423 | + """Returns the instruction sequence for converting the given |
| 424 | + type stored in DE,HL to fixed DE,HL. |
| 425 | + """ |
| 426 | + output: List[str] = [] # List of instructions |
| 427 | + |
| 428 | + if stype == "f": |
| 429 | + return output # Nothing to do |
| 430 | + |
| 431 | + if stype == "f16": |
| 432 | + output.append(runtime_call(RuntimeLabel.F16TOFREG)) |
| 433 | + return output |
| 434 | + |
| 435 | + # If we reach this point, it's an integer type |
| 436 | + if stype == "u8": |
| 437 | + output.append(runtime_call(RuntimeLabel.U8TOFREG)) |
| 438 | + elif stype == "i8": |
| 439 | + output.append(runtime_call(RuntimeLabel.I8TOFREG)) |
| 440 | + else: |
| 441 | + output = to_long(stype) |
| 442 | + if stype in ("i16", "i32"): |
| 443 | + output.append(runtime_call(RuntimeLabel.I32TOFREG)) |
| 444 | + else: |
| 445 | + output.append(runtime_call(RuntimeLabel.U32TOFREG)) |
| 446 | + |
| 447 | + return output |
| 448 | + |
| 449 | + |
| 450 | +def new_ASMID(): |
| 451 | + """Returns a new unique ASM block id""" |
| 452 | + |
| 453 | + result = "##ASM%i" % src.arch.z80.backend.common.ASMCOUNT |
| 454 | + src.arch.z80.backend.common.ASMCOUNT += 1 |
| 455 | + return result |
0 commit comments