From 462cf6933803b487f1509d8c93d6fa51e84cc9b2 Mon Sep 17 00:00:00 2001 From: Jose Rodriguez Date: Thu, 1 Jan 2026 19:39:23 +0000 Subject: [PATCH 1/2] fix: do not optimize built-in calls Sentences like: LET c = USR(x) are optimized if c var is not used. But the built-in call must be executed if it has side-effects. Thse builtins are: IN, RND and USR --- src/api/optimize.py | 24 ++++++++++++++---------- src/arch/z80/visitor/translator.py | 2 ++ src/symbols/builtin.py | 3 ++- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/api/optimize.py b/src/api/optimize.py index 380ac699b..ff744fa7d 100644 --- a/src/api/optimize.py +++ b/src/api/optimize.py @@ -321,16 +321,20 @@ def visit_LET(self, node): lvalue = node.children[0] if self.O_LEVEL > 1 and not lvalue.accessed: warning_not_used(lvalue.lineno, lvalue.name, fname=lvalue.filename) - block = symbols.BLOCK( - *[ - symbols.CALL(x.entry, x.args, x.lineno, lvalue.filename) - for x in self.filter_inorder( - node.children[1], - lambda x: x.token == "FUNCCALL", - lambda x: x.token != "FUNCTION", - ) - ] - ) + nodes = [ + symbols.CALL(x.entry, x.args, x.lineno, lvalue.filename) if x.token == "FUNCCALL" else x + for x in self.filter_inorder( + node.children[1], + lambda x: x.token in ("FUNCCALL", "BUILTIN"), + lambda x: x.token != "FUNCTION", + ) + if x.token == "FUNCCALL" or getattr(x, "fname") in {"IN", "RND", "USR"} + ] + for node_ in nodes: + if node_.token == "BUILTIN": + node_.discard_result = True + + block = symbols.BLOCK(*nodes) yield block else: yield (yield self.generic_visit(node)) diff --git a/src/arch/z80/visitor/translator.py b/src/arch/z80/visitor/translator.py index 601cdbd98..e027a57f8 100644 --- a/src/arch/z80/visitor/translator.py +++ b/src/arch/z80/visitor/translator.py @@ -152,6 +152,8 @@ def visit_BUILTIN(self, node): att = f"visit_{node.fname}" if hasattr(bvisitor, att): yield getattr(bvisitor, att)(node) + if node.discard_result: + self.ic_fparam(node.type_, optemps.new_t()) return raise InvalidBuiltinFunctionError(node.fname) diff --git a/src/symbols/builtin.py b/src/symbols/builtin.py index 5c512deac..f23e24dc1 100644 --- a/src/symbols/builtin.py +++ b/src/symbols/builtin.py @@ -13,7 +13,7 @@ class SymbolBUILTIN(Symbol): - """Defines an BUILTIN function e.g. INKEY$(), RND() or LEN""" + """Defines a BUILTIN function e.g. INKEY$(), RND() or LEN""" def __init__(self, lineno, fname, type_=None, *operands): assert isinstance(lineno, int) @@ -22,6 +22,7 @@ def __init__(self, lineno, fname, type_=None, *operands): self.lineno = lineno self.fname = fname self.type_ = type_ + self.discard_result = False # Whether to discard the return value of the function @property def type_(self): From 7a7c522efdfb7a037bfe553191b98cbf9f608483 Mon Sep 17 00:00:00 2001 From: Jose Rodriguez Date: Thu, 1 Jan 2026 19:51:46 +0000 Subject: [PATCH 2/2] test: add tests --- tests/functional/arch/zx48k/opt2_in_opt.asm | 38 ++++++ tests/functional/arch/zx48k/opt2_in_opt.bas | 3 + tests/functional/arch/zx48k/opt2_rnd_opt.asm | 127 +++++++++++++++++++ tests/functional/arch/zx48k/opt2_rnd_opt.bas | 3 + tests/functional/arch/zx48k/opt2_usr_opt.asm | 73 +++++++++++ tests/functional/arch/zx48k/opt2_usr_opt.bas | 3 + 6 files changed, 247 insertions(+) create mode 100644 tests/functional/arch/zx48k/opt2_in_opt.asm create mode 100644 tests/functional/arch/zx48k/opt2_in_opt.bas create mode 100644 tests/functional/arch/zx48k/opt2_rnd_opt.asm create mode 100644 tests/functional/arch/zx48k/opt2_rnd_opt.bas create mode 100644 tests/functional/arch/zx48k/opt2_usr_opt.asm create mode 100644 tests/functional/arch/zx48k/opt2_usr_opt.bas diff --git a/tests/functional/arch/zx48k/opt2_in_opt.asm b/tests/functional/arch/zx48k/opt2_in_opt.asm new file mode 100644 index 000000000..a6e665656 --- /dev/null +++ b/tests/functional/arch/zx48k/opt2_in_opt.asm @@ -0,0 +1,38 @@ + org 32768 +.core.__START_PROGRAM: + di + push ix + push iy + exx + push hl + exx + ld (.core.__CALL_BACK__), sp + ei + jp .core.__MAIN_PROGRAM__ +.core.__CALL_BACK__: + DEFW 0 +.core.ZXBASIC_USER_DATA: + ; Defines USER DATA Length in bytes +.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA + .core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN + .core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA +.core.ZXBASIC_USER_DATA_END: +.core.__MAIN_PROGRAM__: + ld bc, 0 + in a, (c) + ld hl, 0 + ld b, h + ld c, l +.core.__END_PROGRAM: + di + ld hl, (.core.__CALL_BACK__) + ld sp, hl + exx + pop hl + pop iy + pop ix + exx + ei + ret + ;; --- end of user code --- + END diff --git a/tests/functional/arch/zx48k/opt2_in_opt.bas b/tests/functional/arch/zx48k/opt2_in_opt.bas new file mode 100644 index 000000000..6d8e01008 --- /dev/null +++ b/tests/functional/arch/zx48k/opt2_in_opt.bas @@ -0,0 +1,3 @@ +REM USR 0 must be compiled despite c being optimized +LET c = IN 0 + diff --git a/tests/functional/arch/zx48k/opt2_rnd_opt.asm b/tests/functional/arch/zx48k/opt2_rnd_opt.asm new file mode 100644 index 000000000..de07cdd67 --- /dev/null +++ b/tests/functional/arch/zx48k/opt2_rnd_opt.asm @@ -0,0 +1,127 @@ + org 32768 +.core.__START_PROGRAM: + di + push ix + push iy + exx + push hl + exx + ld (.core.__CALL_BACK__), sp + ei + jp .core.__MAIN_PROGRAM__ +.core.__CALL_BACK__: + DEFW 0 +.core.ZXBASIC_USER_DATA: + ; Defines USER DATA Length in bytes +.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA + .core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN + .core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA +.core.ZXBASIC_USER_DATA_END: +.core.__MAIN_PROGRAM__: + call .core.RND + ld hl, 0 + ld b, h + ld c, l +.core.__END_PROGRAM: + di + ld hl, (.core.__CALL_BACK__) + ld sp, hl + exx + pop hl + pop iy + pop ix + exx + ei + ret + ;; --- end of user code --- +#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/random.asm" + ; RANDOM functions + push namespace core +RANDOMIZE: + ; Randomize with 32 bit seed in DE HL + ; if SEED = 0, calls ROM to take frames as seed + PROC + LOCAL TAKE_FRAMES + LOCAL FRAMES + ld a, h + or l + or d + or e + jr z, TAKE_FRAMES + ld (RANDOM_SEED_LOW), hl + ld (RANDOM_SEED_HIGH), de + ret +TAKE_FRAMES: + ; Takes the seed from frames + ld hl, (FRAMES) + ld (RANDOM_SEED_LOW), hl + ld hl, (FRAMES + 2) + ld (RANDOM_SEED_HIGH), hl + ret + FRAMES EQU 23672 + ENDP + RANDOM_SEED_HIGH EQU RAND+1 ; RANDOM seed, 16 higher bits + RANDOM_SEED_LOW EQU 23670 ; RANDOM seed, 16 lower bits +RAND: + PROC + ld de,0C0DEh ; yw -> zt + ld hl,(RANDOM_SEED_LOW) ; xz -> yw + ld (RANDOM_SEED_LOW),de ; x = y, z = w + ld a,e ; w = w ^ ( w << 3 ) + add a,a + add a,a + add a,a + xor e + ld e,a + ld a,h ; t = x ^ (x << 1) + add a,a + xor h + ld d,a + rra ; t = t ^ (t >> 1) ^ w + xor d + xor e + ld d,l ; y = z + ld e,a ; w = t + ld (RANDOM_SEED_HIGH),de + ret + ENDP +RND: + ; Returns a FLOATING point integer + ; using RAND as a mantissa + PROC + LOCAL RND_LOOP + call RAND + ; BC = HL since ZX BASIC uses ED CB A registers for FP + ld b, h + ld c, l + ld a, e + or d + or c + or b + ret z ; Returns 0 if BC=DE=0 + ; We already have a random 32 bit mantissa in ED CB + ; From 0001h to FFFFh + ld l, 81h ; Exponent + ; At this point we have [0 .. 1) FP number; + ; Now we must shift mantissa left until highest bit goes into carry + ld a, e ; Use A register for rotating E faster (using RLA instead of RL E) +RND_LOOP: + dec l + sla b + rl c + rl d + rla + jp nc, RND_LOOP + ; Now undo last mantissa left-shift once + ccf ; Clears carry to insert a 0 bit back into mantissa -> positive FP number + rra + rr d + rr c + rr b + ld e, a ; E must have the highest byte + ld a, l ; exponent in A + ret + ENDP + pop namespace +#line 18 "arch/zx48k/opt2_rnd_opt.bas" + END diff --git a/tests/functional/arch/zx48k/opt2_rnd_opt.bas b/tests/functional/arch/zx48k/opt2_rnd_opt.bas new file mode 100644 index 000000000..bf14adb3f --- /dev/null +++ b/tests/functional/arch/zx48k/opt2_rnd_opt.bas @@ -0,0 +1,3 @@ +REM USR 0 must be compiled despite c being optimized +LET c = RND + diff --git a/tests/functional/arch/zx48k/opt2_usr_opt.asm b/tests/functional/arch/zx48k/opt2_usr_opt.asm new file mode 100644 index 000000000..d43d09267 --- /dev/null +++ b/tests/functional/arch/zx48k/opt2_usr_opt.asm @@ -0,0 +1,73 @@ + org 32768 +.core.__START_PROGRAM: + di + push ix + push iy + exx + push hl + exx + ld (.core.__CALL_BACK__), sp + ei + jp .core.__MAIN_PROGRAM__ +.core.__CALL_BACK__: + DEFW 0 +.core.ZXBASIC_USER_DATA: + ; Defines USER DATA Length in bytes +.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA + .core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN + .core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA +.core.ZXBASIC_USER_DATA_END: +.core.__MAIN_PROGRAM__: + ld hl, 0 + call .core.USR + ld hl, 0 + ld b, h + ld c, l +.core.__END_PROGRAM: + di + ld hl, (.core.__CALL_BACK__) + ld sp, hl + exx + pop hl + pop iy + pop ix + exx + ei + ret + ;; --- end of user code --- +#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/usr.asm" + ; Emulates the USR Sinclair BASIC function + ; Result value returns in BC + ; We use HL for returning values, su we must + ; copy BC into HL before returning + ; + ; The incoming parameter is HL (Address to JUMP) + ; +#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/table_jump.asm" + push namespace core +JUMP_HL_PLUS_2A: ; Does JP (HL + A*2) Modifies DE. Modifies A + add a, a +JUMP_HL_PLUS_A: ; Does JP (HL + A) Modifies DE + ld e, a + ld d, 0 +JUMP_HL_PLUS_DE: ; Does JP (HL + DE) + add hl, de + ld e, (hl) + inc hl + ld d, (hl) + ex de, hl +CALL_HL: + jp (hl) + pop namespace +#line 10 "/zxbasic/src/lib/arch/zx48k/runtime/usr.asm" + push namespace core +USR: + push ix ; must preserve IX + call CALL_HL + pop ix + ld h, b + ld l, c + ret + pop namespace +#line 19 "arch/zx48k/opt2_usr_opt.bas" + END diff --git a/tests/functional/arch/zx48k/opt2_usr_opt.bas b/tests/functional/arch/zx48k/opt2_usr_opt.bas new file mode 100644 index 000000000..9e2df382a --- /dev/null +++ b/tests/functional/arch/zx48k/opt2_usr_opt.bas @@ -0,0 +1,3 @@ +REM USR 0 must be compiled despite c being optimized +LET c = USR 0 +