This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

ca65 page crossing assert macro

ca65 page crossing assert macro
by on (#176779)
I've usually just ensured that branches don't cross a page with small pieces of aligned code, but I thought it might be useful to have an assert to verify it. Here's code that should do it:

Code:
.macro assert_branch_page label_
   .assert >(label_) = >(*+2), error, "Page crossing detected!"
.endmacro

test:
   assert_branch_page :+
   beq :+ ; .assert should happen if this would cross a page
      nop
   :
   rts


This seems to work fine, but I'd appreciate a second set of eyes on it, just to be sure. Does this look correct to you? Is *+2 before a branch instruction the right value to test against the label?


Edit: Later on I decided to just put the assert immediately after the branch instruction rather than before, which eliminates the need for +2.
Re: ca65 page crossing assert macro
by on (#178519)
Just tested it myself; I believe it is correct.

6502.org says "A page boundary crossing occurs when the branch destination is on a different page than the instruction AFTER the branch instruction", which is exactly what this macro detects.
Re: ca65 page crossing assert macro
by on (#178522)
If the assert comes before the instruction, then yes, I believe you have to compensate for the size of the instruction itself. I've implemented this functionality a bit differently - I have two macros, one for general page crossing checks, which can be used for data tables or anything else that isn't a branch instruction, and another one specific for branches, which outputs the branch instruction itself (so I only have to write the target label once in my source code) and then checks whether the branch will cause a page to be crossed:

Code:
   ;Generates a warning if two address are in different memory pages.
   .macro Assembler_TestPageCrossing _FirstAddress, _SecondAddress
      .ifblank _SecondAddress
         .assert >_FirstAddress = >*, warning, "Unintentional page crossing."
      .else
         .assert >_FirstAddress = >_SecondAddress, warning, "Unintentional page crossing."
      .endif
   .endmacro

   ;Outputs a branch instruction and generates a warning if the destination is in another memory page.
   .macro Assembler_BranchToSamePage _Instruction, _DestinationAddress
      _Instruction _DestinationAddress
      Assembler_TestPageCrossing _DestinationAddress
   .endmacro

Code:
Test:
   Assembler_BranchToSamePage beq, Destination
   ;(more stuff)
Destination:
Re: ca65 page crossing assert macro
by on (#178523)
Thanks for the replies.

In the weeks since I asked this question, I realized that I could just use the macro immediately after the branch instead and not have to do the +2 (and could also just use the same macro for page crossings everywhere, e.g. RAM regions to be indexed).

My question was really just "is the page crossing from PC + 2 of the branch?" Cause a lot of documentation isn't very explicit about it. Makes sense, though. PC gets adjusted by +2 by the CPU automatically, like it would for any 2 byte instruction, and then branch just optionally adds to it,
Re: ca65 page crossing assert macro
by on (#178529)
Is it possible to have every global label be automatically checked against this? I expected that ca65 would give a warning if any label crossed a page boundary, given the ruinous behavior when that happens, but if that's not the case then having an assertion for every global label that could be a routine would be nice.

jsr is not jmp
Re: ca65 page crossing assert macro
by on (#178531)
What do you mean by ruinous behaviour? The only consequence of crossing a page is it takes the CPU 2 more cycles. Unless you're working on timing critical code it doesn't make a difference.

What's a "global label that could be a routine"?
Re: ca65 page crossing assert macro
by on (#178534)
The only "ruinous" thing I can think of related to page crossing is an indirect JMP that loads the destination address from the last byte of a page, because the CPU wraps around to the beginning of the page for the second byte of the address, instead of advancing to the next page.

Other than that, page crossing is hardly a big deal on the NES. Sure you have to avoid it in timed code, but that's hardly a big part of a typical NES game. A simple macro should cover these cases without problems.
Re: ca65 page crossing assert macro
by on (#178535)
tokumaru wrote:
The only "ruinous" thing I can think of related to page crossing is an indirect JMP that loads the destination address from the last byte of a page, because the CPU wraps around to the beginning of the page for the second byte of the address, instead of advancing to the next page.

That generates this warning: "jmp (abs)" across page border
Re: ca65 page crossing assert macro
by on (#178538)
That's my mistake - I was confused, and thought of jsr suffering from that problem, rather than jmp.
Re: ca65 page crossing assert macro
by on (#178541)
Well, it's also only indirect jmp, which I think is exceedingly rare to fall on a page boundary (there just aren't many relevant use cases), and easily prevented. Direct jmp doesn't have this problem.