| |
cadaver
Registered: Feb 2002 Posts: 1154 |
Do you analyze page crossings / try to eliminate them?
Particularly in a game context, where code isn't easily separated into hot paths + data can be spread out in a manner that is not conductive to page-aligning everything cleanly?
As for the methods, I personally do that by symbol table analysis + a customizable simple emulator for runtime detection & counting ( https://github.com/cadaver/oldschoolengine2 ) |
|
| |
chatGPZ
Registered: Dec 2001 Posts: 11148 |
I put warnings in the code when this becomes relevant. For generic code its probably a bit harder to do though. |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1381 |
I added instrumentation to a local copy of unp64 and generated graphs of how many cycles per run were spent by each instruction back when I was optimising the unpacker for tinycrunch, but that was all on a single page.
I think I put some work into avoiding page crossings in the sofft-blitter for a koala bouncer at some point, but that's not been released yet. |
| |
JackAsser
Registered: Jun 2002 Posts: 1994 |
Quote: I put warnings in the code when this becomes relevant. For generic code its probably a bit harder to do though.
Same here |
| |
DanPhillips
Registered: Jan 2003 Posts: 30 |
For Armalyte Ultra anything that's using an index register for lookups into tables has it's tables moved so the index can't cross a page boundary.
More done by "knowing the code" rather than measuring ;)
And a lot of the code is now completely unrolled, so a lot less indexing/loops.
Cheers
Dan |
| |
oziphantom
Registered: Oct 2014 Posts: 480 |
64tass has a build in page boundary check command for you to use. It can detect branches across a page and you can use it to make sure that a "section" or "table" is all on the same page as well. |
| |
aeeben
Registered: May 2002 Posts: 42 |
There's probably a smarter way to do it, but I just use a macro for page boundary check in DASM:
MAC pw
if (*>>8) != ({1}>>8)
echo "WARNING: Page boundary crossed at",{1},"(grep addr main.sym to find label)"
endif
ENDM
I've mostly used it in raster interrupts or when copying or initializing large blocks of memory. Also, I place any such subroutines near the start of program (as the first include's), so they're not pushed forward every time when writing more code.
IRQ subroutine
pha
lda #$01
sta $d019
lda #$10
timing1
.1
asl
bcc .1
pw timing1
nop
.
.
For data, I always page-align everything - a bad habit stuck from Game Boy world I guess :D |
| |
Frostbyte
Registered: Aug 2003 Posts: 174 |
Hmm interesting. This motivated me to look at KickAssembler docs, seems that there is no automagic option to warn/error about page crossings at compile time, but for branches there is a suggestion to do it manually like so:
beq label1
.errorif (>*) != (>label1), "Branch crosses a page!"
nop // some code to branch over
nop
label1:
Thus I suppose the above could also be used for tables:
table1:
nop // some table data
nop
.errorif (>*) != (>table1), "Table crosses a page!"
A build option |
| |
chatGPZ
Registered: Dec 2001 Posts: 11148 |
that's basically what i do, except i don't use a macro (i avoid them unless i really need them) :) |
| |
Frostbyte
Registered: Aug 2003 Posts: 174 |
Fucking "you cannot edit this post".
A build option would be very nice though as the above litters the code a bit and you need to remember to put the checks in place yourself. |
| |
chatGPZ
Registered: Dec 2001 Posts: 11148 |
An option to warn at all page crossings wouldn't be terribly useful for most non trivial programs - you'd just drown in warnings :) |
| |
Frostbyte
Registered: Aug 2003 Posts: 174 |
Quote: An option to warn at all page crossings wouldn't be terribly useful for most non trivial programs - you'd just drown in warnings :)
Not useful for most scenarios, agreed - that's why it'd be a build option. Enable it when you need it. :) |
| |
cadaver
Registered: Feb 2002 Posts: 1154 |
Nice macro approach!
I also try to put the already ready or "least modified" code to the beginning, though it's made harder when I insist on stupid things like falling through to main loop from some long-winded initialization :) |
| |
trident
Registered: May 2002 Posts: 79 |
i use kickassembler and a set of macros that look like this:
loop:
// some code
bne_page(loop)
that trigger an error (or warning / assert) if that bne crosses a page boundary.
inside the bne_page() macro is the same type of macro magic that frostbyte posted.
sometimes you want a page boundary to be crossed (typically when doing really tightly timed rastercode). for this usecase there is a second set of macros:
loop:
// some code that intentionally crosses a page boundary
bne_different_page(loop) |
| |
Peiselulli
Registered: Oct 2006 Posts: 81 |
ACME macros:
!macro check_same_page_start {
!set page_check_page_addr = * & $ff00
}
!macro check_same_page_end {
!if (page_check_page_addr != ( * & $ff00)) {
!error "not the same page"
}
}
I place this macros around the interesting code (stable or highly optimized) |
| |
JackAsser
Registered: Jun 2002 Posts: 1994 |
Quote: Hmm interesting. This motivated me to look at KickAssembler docs, seems that there is no automagic option to warn/error about page crossings at compile time, but for branches there is a suggestion to do it manually like so:
beq label1
.errorif (>*) != (>label1), "Branch crosses a page!"
nop // some code to branch over
nop
label1:
Thus I suppose the above could also be used for tables:
table1:
nop // some table data
nop
.errorif (>*) != (>table1), "Table crosses a page!"
A build option
That's exactly how I do it for critical sections such as a clock slide.
I generally don't align data or code if I don't have to, hence the error hints to me when I must to. (to save space generally). |
| |
ws
Registered: Apr 2012 Posts: 230 |
Cool solutions here!
Actually, i never thought about that before, because CBMprgStudio warns automatically on compile that branches or commands cross page boundaries. But tables i always aligned manually until now and observed behavior in RAM by monitoring with ICU64/Retrodebugger. |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1381 |
While we're swapping notes, this is what I use in ca65 to fail compilation if somethings no longer on the same page as the address passed in:
.macro assert_same_page label_
.assert >(label_) = >(*), error, "Page crossing detected!"
.endmacro
Then here's an example clock slide:
sta :+ +1
: bcc :-
assert_same_page(:+)
.byte $80,$80,$80,$80
.byte $80,$80,$80,$04,$ea
:
I do like Trident's bxx_page/bxx_different_page concept mind - I might have to steal that :) |
| |
tlr
Registered: Sep 2003 Posts: 1727 |
I too approve of tridents solution. It is a nice complement, well worth stealing. :)
For a long time I used these macros in dasm: MAC START_SAMEPAGE
_SAMEPAGE_MARKER SET .
_SAMEPAGE_NAME SET {0}
ENDM
MAC END_SAMEPAGE
IF >_SAMEPAGE_MARKER != >.
IF _SAMEPAGE_NAME!=""
ERROR "[SAMEPAGE] Page crossing not allowed! (",_SAMEPAGE_NAME,"@",_SAMEPAGE_MARKER,"-",.,")"
ELSE
ERROR "[SAMEPAGE] Page crossing not allowed! (",_SAMEPAGE_MARKER,"-",.,")"
ENDIF
ELSE
IF _SAMEPAGE_NAME!=""
INFO "[SAMEPAGE] successful SAMEPAGE. (",_SAMEPAGE_NAME,"@",_SAMEPAGE_MARKER,"-",.,")"
ELSE
INFO "[SAMEPAGE] successful SAMEPAGE. (",_SAMEPAGE_MARKER,"-",.,")"
ENDIF
ENDIF
ENDM Note the capitals on pseudo ops. I don't do that anymore. :)
These can be used with START_SAMEPAGE <name> and END_SAMEPAGE to get a nice visual on what is happening. INFO: [SAMEPAGE] successful SAMEPAGE. ( get_byte @ $5d8 - $5dd ) Nowadays I've switched to assembler provided pseudo ops. |
| |
Boogaloo
Registered: Aug 2019 Posts: 21 |
I also use pretty much the same solution as trident, in kickassembler. Nice and clean. |
| |
Bitbreaker
Registered: Oct 2002 Posts: 501 |
Similiar to what Peiselulli does in ACME, but by comparing highbyte of PC/label, also in ACME here:
!if >* != >.ld_gloop { !error "getloop code crosses page!" }
bcs .ld_gloop
|
| |
Frostbyte
Registered: Aug 2003 Posts: 174 |
One more for KickAssembler: The page crossing checks can also be done in pseudo commands. Not a big difference vs Trident's macro approach, maybe just makes the source look a little bit cleaner as less parentheses and shit. :)
BasicUpstart2(main)
* = $08f8
main:
lda #1
bneap skip
bnesp skip
nop
nop
nop
nop
skip: sta $d020
jmp *
.pseudocommand bnesp addr {
.errorif (>*) != (>addr.getValue()), "Branch taken to another page"
bne addr
}
.pseudocommand bneap addr {
.errorif (>*) == (>addr.getValue()), "Branch stays on the same page"
bne addr
}
Here the bneap (BNE to Another Page) passes but bnesp (BNE to Same Page) throws an error, that helpfully also states which line the error originates from:
Error: Branch taken to another page
at line 15, column 2 in page_safe_branch_test.asm
called at line 6, column 2 in page_safe_branch_test.asm |
| |
Dano
Registered: Jul 2004 Posts: 228 |
Really dig that pseudocommand approach! |
| |
Frostbyte
Registered: Aug 2003 Posts: 174 |
OK, last one from me about this, I promise! :D Changed the pseudo command names to ***3 and ***4 to indicate how many cycles we wish for the branching to take. Here's a complete set of pseudo commands for cycle-asserted branching:
.pseudocommand bcc3 addr { assertSamePage(addr) ; bcc addr }
.pseudocommand bcc4 addr { assertDifferentPage(addr) ; bcc addr }
.pseudocommand bcs3 addr { assertSamePage(addr) ; bcs addr }
.pseudocommand bcs4 addr { assertDifferentPage(addr) ; bcs addr }
.pseudocommand beq3 addr { assertSamePage(addr) ; beq addr }
.pseudocommand beq4 addr { assertDifferentPage(addr) ; beq addr }
.pseudocommand bne3 addr { assertSamePage(addr) ; bne addr }
.pseudocommand bne4 addr { assertDifferentPage(addr) ; bne addr }
.pseudocommand bmi3 addr { assertSamePage(addr) ; bmi addr }
.pseudocommand bmi4 addr { assertDifferentPage(addr) ; bmi addr }
.pseudocommand bpl3 addr { assertSamePage(addr) ; bpl addr }
.pseudocommand bpl4 addr { assertDifferentPage(addr) ; bpl addr }
.pseudocommand bvc3 addr { assertSamePage(addr) ; bvc addr }
.pseudocommand bvc4 addr { assertDifferentPage(addr) ; bvc addr }
.pseudocommand bvs3 addr { assertSamePage(addr) ; bvs addr }
.pseudocommand bvs4 addr { assertDifferentPage(addr) ; bvs addr }
.macro assertSamePage(addr) {
.errorif (>*) != (>addr.getValue()), "Expected a 3 cycle branch to same page, but branching to another page"
}
.macro assertDifferentPage(addr) {
.errorif (>*) == (>addr.getValue()), "Expected a 4 cycle branch to another page, but branching to same page"
}
|
| |
ChristopherJam
Registered: Aug 2004 Posts: 1381 |
The error check needs to go after the branch, no? |
| |
chatGPZ
Registered: Dec 2001 Posts: 11148 |
Now, can we expand this to using static analysis for indexed instructions? :) |
| |
Frostbyte
Registered: Aug 2003 Posts: 174 |
Good catch ChristopherJam! I never thought about it more carefully, but indeed it's the page we're on AFTER the branch command that counts. As tested:
.C:08fd F0 11 BEQ $0910 - A:00 X:00 Y:00 SP:f6 ..-...Z. 5613633
.C:0910 4C 10 09 JMP $0910 - A:00 X:00 Y:00 SP:f6 ..-...Z. 5613637
.C:08fe F0 10 BEQ $0910 - A:00 X:00 Y:00 SP:f6 ..-...Z. 5754443
.C:0910 4C 10 09 JMP $0910 - A:00 X:00 Y:00 SP:f6 ..-...Z. 5754446
.C:08fd F0 F1 BEQ $08F0 - A:00 X:00 Y:00 SP:f6 ..-...Z. 5726847
.C:08f0 4C F0 08 JMP $08F0 - A:00 X:00 Y:00 SP:f6 ..-...Z. 5726850
.C:08fe F0 F0 BEQ $08F0 - A:00 X:00 Y:00 SP:f6 ..-...Z. 5604241
.C:08f0 4C F0 08 JMP $08F0 - A:00 X:00 Y:00 SP:f6 ..-...Z. 5604245
And the revised pseudo commands:
.pseudocommand bcc3 addr { bcc addr ; assertSamePage(addr) }
.pseudocommand bcc4 addr { bcc addr ; assertDifferentPage(addr) }
.pseudocommand bcs3 addr { bcs addr ; assertSamePage(addr) }
.pseudocommand bcs4 addr { bcs addr ; assertDifferentPage(addr) }
.pseudocommand beq3 addr { beq addr ; assertSamePage(addr) }
.pseudocommand beq4 addr { beq addr ; assertDifferentPage(addr) }
.pseudocommand bne3 addr { bne addr ; assertSamePage(addr) }
.pseudocommand bne4 addr { bne addr ; assertDifferentPage(addr) }
.pseudocommand bmi3 addr { bmi addr ; assertSamePage(addr) }
.pseudocommand bmi4 addr { bmi addr ; assertDifferentPage(addr) }
.pseudocommand bpl3 addr { bpl addr ; assertSamePage(addr) }
.pseudocommand bpl4 addr { bpl addr ; assertDifferentPage(addr) }
.pseudocommand bvc3 addr { bvc addr ; assertSamePage(addr) }
.pseudocommand bvc4 addr { bvc addr ; assertDifferentPage(addr) }
.pseudocommand bvs3 addr { bvs addr ; assertSamePage(addr) }
.pseudocommand bvs4 addr { bvs addr ; assertDifferentPage(addr) }
And I suppose the table check needs a fix too, as it's off by one (would error when the last element of the table is at $xxff)
table1:
nop // some table data
nop
.errorif (>*-1) != (>table1), "Table crosses a page!" |
| |
Frantic
Registered: Mar 2003 Posts: 1630 |
If someone would happen to feel like it, don't hesitate to write a short article on this topic on codebase64.
https://codebase64.org/doku.php?id=start
By the way: I just realised that the Codebase64 wiki has existed for 17 years now. Quite a while. :)
//FTC |
| |
Martin Piper
Registered: Nov 2007 Posts: 646 |
I sometimes use macros like this:
!macro lobcs .target {
!if (* - .target) <= 126 {
bcs .target
} else {
bcc +
jmp .target
+
}
}
.copy
... Code that might grow or shrink depending on zeropage usage etc ...
+lobcs .copy
|
| |
Oswald
Registered: Apr 2002 Posts: 5029 |
Quote: If someone would happen to feel like it, don't hesitate to write a short article on this topic on codebase64.
https://codebase64.org/doku.php?id=start
By the way: I just realised that the Codebase64 wiki has existed for 17 years now. Quite a while. :)
//FTC
thank you for keeping it alive, I bet it helped countless coders throughout the years. I use it to steal some routines from time to time I know. |
| |
Oswald
Registered: Apr 2002 Posts: 5029 |
Quote: I sometimes use macros like this:
!macro lobcs .target {
!if (* - .target) <= 126 {
bcs .target
} else {
bcc +
jmp .target
+
}
}
.copy
... Code that might grow or shrink depending on zeropage usage etc ...
+lobcs .copy
with a switch 64tass does this automatically for you, every too far branch is replaced like that.
.page
...
.endp
if a page is crossed between .page and .endp you get a warning. |
| |
Martin Piper
Registered: Nov 2007 Posts: 646 |
Quote: with a switch 64tass does this automatically for you, every too far branch is replaced like that.
.page
...
.endp
if a page is crossed between .page and .endp you get a warning.
Yeah I don't want a switch to have that for every branch, I want more control. I never really liked TASS, it never felt like a good assembler. |
| |
Krill
Registered: Apr 2002 Posts: 2855 |
Quoting Martin PiperI never really liked TASS, it never felt like a good assembler. Care to elaborate? :)
I, for one, prefer my assembler's pseudo-ops not to scream at me (and the assembler itself to accept canonical syntax). |
| |
chatGPZ
Registered: Dec 2001 Posts: 11148 |
--fullstop to the rescue :D
I prefer ACME too, only because the dev is on IRC so i can yell at him when something doesn't work as expected :) |
| |
Martin Piper
Registered: Nov 2007 Posts: 646 |
Quote: Quoting Martin PiperI never really liked TASS, it never felt like a good assembler. Care to elaborate? :)
I, for one, prefer my assembler's pseudo-ops not to scream at me (and the assembler itself to accept canonical syntax).
It's mostly the syntax. For example, having "." used for directives feels very wrong. For me a "." is meant to be a local label, and a label without is a global label.
Then after .text and .byte etc we have functions, like binary(), suddenly appear with a different syntax and naming convention again.
I really just get a massive headache every time I look at TASS stuff.
I've tweaked ACME to include much better label resolving and code generation that can settle on stable addresses instead of aborting the assembly. I've also added python support for inline assembly and data generation. With these additions, I don't need TASS. |
| |
Raistlin
Registered: Mar 2007 Posts: 575 |
We're veering off topic here but I prefer KickAss .. for reasons hinted at in its name. I wish it wasn't Java-based, of course, but that's not such a huge bugbear. |
| |
Frantic
Registered: Mar 2003 Posts: 1630 |
Quote: thank you for keeping it alive, I bet it helped countless coders throughout the years. I use it to steal some routines from time to time I know.
👍 Credits should also go to CountZero for hosting, and Moloch for providing the domain name. |
| |
soci
Registered: Sep 2003 Posts: 474 |
Quoting Martin PiperI've tweaked ACME to include much better label resolving and code generation that can settle on stable addresses instead of aborting the assembly. I've also added python support for inline assembly and data generation. With these additions, I don't need TASS.
Hmm, interesting. I take it as a compliment.
Back on topic I do align stuff but only as needed. Did profiling once in a while like on cfsfsck where it was not obvious what takes the most time. But for effects it's usually easy to know which loop(s) need such branch/table optimizations.
Btw. while listing all page crossing branches can be insightful too that's not how it goes. As mentioned above page checks for branch or table page crosses are added where these matter. Sometime in the past I got a friendly reminder that these two crossings are not exactly the same so there's a way to distinguish which one is meant.
Assertions are good but they do trigger eventually and then comes the code reshuffling, sticking stuff to explicit addresses or adding of alignments. To avoid the first two I did some work last year on alignments ( https://tass64.sourceforge.net/#alignment ). Just so I don't need to worry about wasting more than what's necessary for interrupt routines with small timing loops in them. Or to only align a critical table if it's really necessary.
Such alignments were possible through creative use of fill directive already so there's nothing new under the sun. |
| |
Martin Piper
Registered: Nov 2007 Posts: 646 |
Quote: We're veering off topic here but I prefer KickAss .. for reasons hinted at in its name. I wish it wasn't Java-based, of course, but that's not such a huge bugbear.
Hah. I've been thinking of converting ACME to Java so I can integrate assembly into testing and profile guided optimisation. It would allow branch optimisation and also choose which labels/data to put in zeropage. |