rgbds icon indicating copy to clipboard operation
rgbds copied to clipboard

[Feature request] Link-time conditionals for identically sized branches

Open Rangi42 opened this issue 4 years ago • 8 comments

Generating code with macros, like pokecrystal16, can require different optimizations depending on where labels end up being placed. (Note the various dbs in that file.) However, if needs to be evaluated at assembly time, and an eventual ternary operator would only work within dbs, requiring people to encode instructions as raw bytes. (For instance, db (HIGH(\1EntriesEnd) & 1) ? $28 : $20, .search_loop - (@ + 1), would eventually work, but (HIGH(\1EntriesEnd) & 1) ? <jr nz, .search_loop> : <jr z, .search_loop>, or jr <(HIGH(\1EntriesEnd) & 1) ? nz : z>, .search_loop, cannot.)

A solution could be link-time conditionals. These would fail to assemble if all their branches are not already the same size, or if one branch defines a label but another does not. (Two branches defining a label in different relative locations should be okay, I think, but if it isn't that would be fine as a limitation too.)

Example syntax (chosen in contrast with static_assert):

dynamic_if HIGH(\1EntriesEnd) & 1
    jr nz, .search_loop
else
    jr z, .search_loop
endc

Rangi42 avatar Apr 02 '21 20:04 Rangi42

I like it. I wonder how complex RPN-based relocations would get, though.

aaaaaa123456789 avatar Apr 03 '21 02:04 aaaaaa123456789

Sadly, this precondition isn't sufficient:

dynamic_if Test & 1
    def trap EQUS ""
else
    def trap EQUS "ld a, 1"
endc

    trap

Both branches produce exactly 0 bytes, so they fulfill the precondition, but then trap breaks everything. Its computation can also be made arbitrarily complex, as long as neither branch outputs any bytes.

ISSOtm avatar Apr 03 '21 08:04 ISSOtm

That's not a problem; the second definition would fail since trap is already defined.

dynamic_if (or some better name; maybe when) would act more like a "ROM union" than an asm-time if: every branch is evaluated, top to bottom as usual, but nothing is output right away; it's stored in RPN buffers along with the RPN conditions. (The assembler fails right away if they're different sizes.) Then the linker outputs only the first one with a true condition.

Rangi42 avatar Apr 03 '21 14:04 Rangi42

ISSOtm suggested "UNION IF" for this, which avoids a new keyword and I think nicely indicates how it works. One concern: would we be able to use ELSE and ENDC, or need to do UNION ELSE and UNION ENDC?

Alternatively, maybe UNION IF cond / ... / NEXTU / ... / ENDU would be fine, since this feature is ROM-only, and regular UNIONs are RAM-only. Or add a new ELSEU keyword (and ELIFU). Or have UNION IF cond / ... / NEXTU IF cond2 / ... / NEXTU / ... / ENDU.

Rangi42 avatar Dec 20 '23 18:12 Rangi42

That last syntax is what I had in mind, yep. The last branch is mandatory, obviously!

ISSOtm avatar Dec 20 '23 18:12 ISSOtm

I was hoping for regular elif and else for other branches, since the union part is already declared, but I wouldn't mind writing it some other equally clear way.

aaaaaa123456789 avatar Dec 20 '23 18:12 aaaaaa123456789

That last syntax is what I had in mind, yep. The last branch is mandatory, obviously!

Like this?

UNION IF x == -1
    jr c, .foo
NEXTU IF x == 0 ; "elif"
    jr z, .foo
NEXTU IF x == 1 ; "elif"
    jr nc, .foo
NEXTU ; "else"
    jr .foo
ENDU ; "endc"

Rangi42 avatar Dec 20 '23 18:12 Rangi42

I was hoping for regular elif and else for other branches, since the union part is already declared, but I wouldn't mind writing it some other equally clear way.

I believe that would be problematic given how IF sets a lexer mode to skip over lines until an ELIF/ELSE/ENDC.

Rangi42 avatar Dec 20 '23 18:12 Rangi42