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:
.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.
Last edited by rainwarrior on Wed Oct 31, 2018 1:27 am, edited 1 time in total.
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.
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:
;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
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,
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
Last edited by mikejmoffitt on Mon Aug 29, 2016 1:40 am, edited 2 times in total.
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.
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.
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
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.