evie-calico / evunit

A CPU emulator for running unit tests on Game Boy code.
MIT License
17 stars 4 forks source link

Expand memory setting capabilities when arranging tests #34

Closed elialm closed 7 months ago

elialm commented 8 months ago

I've been using evunit for a while now, and ran into the particular issue that there is little freedom in writing to memory when arranging tests. The README mentions only this:

Finally, memory can be assigned a value in the config file by surrounding a label name in square brackets. You can either assign an 8-bit integer, a string, or an array of either. Like the flags, memory addresses must be quoted because of the square brackets:

This constraints the developer in only being able to use labels as ways to set memory contents. But what about pushing values to the stack? Or when you just need some memory buffer, but don't care about where it sits? Currently, the developer is forced to pollute the source code with random labels to be able to assign them.

To fix this, I would like to propose a few features:

  1. Allow arbitrary integer values to be used as labels instead. E.g. "[0xC000]" = 0x55 would write the value 0x55 at WRAM address 0xC000. Integer formats should then be the same as supported in TOML to avoid confusion and provide maximum flexibility.
  2. Have some way of setting up the stack. Some functions may have large variables or use a great number of them. A way to pass these values is to push them on the stack. However, there is no easy way to do this using evunit due to the reverse nature of the stack. I have a proposed way of doing this below.
  3. Allow arithmetic in labels. I can do it in RGBDS assembly, why not when testing (e.g. "[some_label+1]" = 0x69)? Although, I don't know if the need of this feature will be made obsolete when implementing the other above features. Having them, I cannot really think of a use-case for this one. It will also probably take some considerable work to implement this, so I'll leave it as a nice-to-have.

Proposed way to set up the stack

A continuation of point 2 above. A way to setup the stack could be having a special field for it (e.g. stack). Then, say we want to push values to the stack, we assign an array to this field with the values we want to push to the stack. In TOML, this could look like:

stack = [ 0x55, 0x66 ]

This would be roughly equivalent to running the following imaginary instructions before executing the test function:

dec sp
ld [sp], $66
dec sp
ld [sp], $55

Loading these values in reverse seems more logical due to the stack working in reverse, but it could work either way. Strings could also be allowed in assigning values on the stack, similar to when assigning to a label (e.g. stack = [ "hello world", 0 ]). Although, I wonder if this will be used.

Using this manner of stack assigning, would leave something to be done with the value assigned to sp. Since the stack is modified, the pointer to it would need to move as well. I propose to initialise it with the user specified value first, then to decrement it as values are pushed unto the stack (as in the assembly example above) and have that modified pointer kept that way as the test is ran. Then when the test function returns, we pop the added values and return to caller, ending the test. I think that is a rather clean way to handle this and shouldn't cause any problems.

To maintain reverse-compatibility, stack would be an optional parameter. If not specified we leave the stack and its pointer as is, keeping the status quo.

Concluding, I would like to hear any thoughts regarding this proposal and then I'll add these features myself.

elialm commented 8 months ago

I was thinking about how to handle the stack and found an issue with pushing that values to the stack. The way I described above wouldn't work, since normally caller sits at the stack pointer address for the call to ret to jump to caller. That would mean that the values would need to be pushed before pushing caller, not after.

As an example, with the following example...

[example]
caller = 0x1234
sp = 0xDFFF
stack = [ 0x55, 0x66 ]

... the stack would look as follows at the start of the test...

| Address | Data |
| ------- | ---- |
| 0xDFFF  | 0x66 |
| 0xDFFE  | 0x55 |
| 0xDFFD  | 0x12 |
| 0xDFFC  | 0x34 |    <-- stack pointer currently points to this

In this manner, ret would still be able to return to the caller while the arguments would be accessible via the stack pointer.

evie-calico commented 8 months ago

These are good ideas; in the mean time, I think you can accomplish the same thing by using evunit as a Rust library instead of via the command line.

elialm commented 8 months ago

It looks like one could do it with TestConfig::memory and changing sp afterwards, but it's not particularly straightforward. I try to avoid using the library, since I rather like the TOML approach. It's easy to read and write.

But I'm glad that you think positively about my proposal. I'll see if I can get around implementing it and keep you updated.

elialm commented 7 months ago

Closing issue, since pull request was merged