rgbds
rgbds copied to clipboard
[Feature request] Link-time conditionals for identically sized branches
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
I like it. I wonder how complex RPN-based relocations would get, though.
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.
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.
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.
That last syntax is what I had in mind, yep. The last branch is mandatory, obviously!
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.
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"
I was hoping for regular
elifandelsefor other branches, since theunionpart 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.