gbdev / rgbds

Rednex Game Boy Development System - An assembly toolchain for the Nintendo Game Boy and Game Boy Color
https://rgbds.gbdev.io
MIT License
1.35k stars 172 forks source link

[Feature request] Test if an expression is constant at assembly time #478

Closed aaaaaa123456789 closed 4 years ago

aaaaaa123456789 commented 4 years ago

Sometimes you want to add sanity checks to macros. Something like this:

if (\1) < 4
  fail "The number of widgets must be at least four."
endc

But those checks require the macro argument to be a compile-time constant. If the macro can be used with expressions that are calculated by the linker, this is not possible.

The current implementation requires removing the check. If an expression can be tested for const-ness, the checks can be kept as long as the expression is constant. This can be a function or even a new conditional statement: after all, if (\1) is a constant expression, so is (\1) < 4, so evaluating some conditional statement only if the expression is constant will do the trick.

pinobatch commented 4 years ago

Compare .const() in ca65 which is used thus:

  .if .const(a + 3)
    .out "a + 3 is constant"
  .else
    .out "a + 3 is not constant"
  .endif
ISSOtm commented 4 years ago

Can't such sanity checks be added with assertions? (#292) I thought about adding such a ISCONST function, but I didn't see any use for it that wouldn't overlap with link-time asserts (which I do plan to add as soon as handling of changing the .o file format is decided on in #470). Also, removing such checks if they can't be done at assembly time doesn't sound like something you'd want, right?

pinobatch commented 4 years ago

I understand ca65 .const as answering "Would this expression be a valid argument to rept or if?" (Ignore for a moment negative numbers being invalid for rept.) Thus if this expression can be evaluated now, return 1. Otherwise, such as if it would need to be deferred until link time, return 0.

ISSOtm commented 4 years ago

That is also exactly how I understand it, what I'm asking about is what contexts this would be useful in, assuming link-time assertions are supported.

aaaaaa123456789 commented 4 years ago

Link-time assertions could solve the exact example I posted above, but anything more complex would be impossible. I've been writing lots of very complex macros lately, with multiple calculations inside, and I've come to the point where having different behaviors if an expression is compile-time constant and if it isn't actually makes sense.

ISSOtm commented 4 years ago

Really? Do you have an example? That sounds like a bug more than anything else

aaaaaa123456789 commented 4 years ago

Might as well. Context: splitting a long array of structs (perhaps too long to fit in a bank) into chunks and creating a table of chunks. The table contains four-byte entries containing count (byte), bank (byte), address (halfword).

This is my current code for the macro that creates an entry: https://gist.github.com/aaaaaa123456789/751d3d5590c5a13d789a25b7269bce6f

You will notice there's a lot of code for dealing with the case of having more than 255 entries. Now, all of that code requires being able to calculate the actual entry count at compile time. If I ever used a non-constant count, it would make perfect sense to just assert that the count fits in a byte and forgo all the impossible calculations. (I'd probably have to restructure the macro a bit to take the actual count as an argument and not the new max index, but that's beside the point.) Doing what you can and covering most common cases is better than doing nothing; there's no reason why I'd have to write a separate macro for non-constant data to get what's essentially the same behavior.

pinobatch commented 4 years ago

In ca65, I've occasionally implemented the instruction set of a different processor connected to the Super NES using a macro pack.

Some of these ISAs have shorter encodings when constant values are in a specific range. For example, to determine whether the ld to ldh optimization is valid, I'd have to say something like .if .const(addr) && (addr) > $100.

Some possible ISAs that one might want to implement atop RGBDS:

ISSOtm commented 4 years ago

Question: do we want ISCONST(symbol), or ISCONST(expression)? There is a major difference, which would be that EQUS are not expanded in the former, but are in the latter.

mid-kid commented 4 years ago

Definitely expression. When you get an argument in a macro it's impossible to know whether you're getting an expression (which might be const depending on what symbols/expressions are used) or not.

ISSOtm commented 4 years ago

Okay then. Note that this means I cannot special-case ISCONST(symbol) to not expand EQUS, as this requires the decision to be taken before lexing any tokens inside the expression, but it can only be taken after lexing at least one.

aaaaaa123456789 commented 4 years ago

Why would not expanding EQUS be a good thing?

ISSOtm commented 4 years ago

For the same reason EQUS are not expanded within DEF() or PURGE.