Log inRegister an accountBrowse CSDbHelp & documentationFacts & StatisticsThe forumsAvailable RSS-feeds on CSDbSupport CSDb Commodore 64 Scene Database
 Welcome to our latest new user Jedfox ! (Registered 2024-05-28) You are not logged in - nap
CSDb User Forums


Forums > C64 Coding > Do you analyze page crossings / try to eliminate them?
2024-03-13 13:16
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 )
2024-03-13 13:18
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.
2024-03-13 13:33
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.
2024-03-13 20:10
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
2024-03-14 01:32
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
2024-03-14 07:41
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.
2024-03-14 14:29
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
2024-03-14 14:37
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
2024-03-14 14:37
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) :)
2024-03-14 14:38
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.
2024-03-14 14:52
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 :)
2024-03-14 15:28
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. :)
2024-03-14 15:51
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 :)
2024-03-14 18:04
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)
2024-03-14 22:05
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)
2024-03-14 22:30
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).
2024-03-15 03:35
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.
2024-03-15 06:20
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 :)
2024-03-15 08:23
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.
2024-03-15 11:08
Boogaloo

Registered: Aug 2019
Posts: 21
I also use pretty much the same solution as trident, in kickassembler. Nice and clean.
2024-03-15 12:57
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
2024-03-15 14:44
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
2024-03-15 16:46
Dano

Registered: Jul 2004
Posts: 228
Really dig that pseudocommand approach!
2024-03-15 17:24
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"
}
2024-03-15 18:09
ChristopherJam

Registered: Aug 2004
Posts: 1381
The error check needs to go after the branch, no?
2024-03-15 18:51
chatGPZ

Registered: Dec 2001
Posts: 11148
Now, can we expand this to using static analysis for indexed instructions? :)
2024-03-15 18:58
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!"
2024-03-15 19:54
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
2024-03-18 10:05
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

2024-03-18 17:01
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.
2024-03-18 17:03
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.
2024-03-19 01:07
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.
2024-03-19 09:16
Krill

Registered: Apr 2002
Posts: 2855
Quoting Martin Piper
I 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).
2024-03-19 13:25
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 :)
2024-03-19 15:21
Martin Piper

Registered: Nov 2007
Posts: 646
Quote: Quoting Martin Piper
I 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.
2024-03-19 16:16
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.
2024-03-19 17:49
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.
2024-03-19 18:49
soci

Registered: Sep 2003
Posts: 474
Quoting Martin Piper
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.

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.
2024-03-20 04:29
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.
RefreshSubscribe to this thread:

You need to be logged in to post in the forum.

Search the forum:
Search   for   in  
All times are CET.
Search CSDb
Advanced
Users Online
elkmoose
Oswald/Resource
Dano/Padua
t0m3000/HF^BOOM!^IBX
zscs
Guests online: 86
Top Demos
1 Next Level  (9.8)
2 13:37  (9.7)
3 Mojo  (9.7)
4 Coma Light 13  (9.7)
5 Aliens in Wonderland  (9.6)
6 Edge of Disgrace  (9.6)
7 No Bounds  (9.6)
8 Comaland 100%  (9.6)
9 Uncensored  (9.6)
10 Wonderland XIV  (9.6)
Top onefile Demos
1 Happy Birthday Dr.J  (9.7)
2 Layers  (9.6)
3 It's More Fun to Com..  (9.6)
4 Cubic Dream  (9.6)
5 Party Elk 2  (9.6)
6 Copper Booze  (9.6)
7 TRSAC, Gabber & Pebe..  (9.5)
8 Rainbow Connection  (9.5)
9 Dawnfall V1.1  (9.5)
10 Daah, Those Acid Pil..  (9.5)
Top Groups
1 Nostalgia  (9.4)
2 Oxyron  (9.3)
3 Booze Design  (9.3)
4 Censor Design  (9.3)
5 SHAPE  (9.3)
Top NTSC-Fixers
1 Pudwerx  (10)
2 Booze  (9.7)
3 Stormbringer  (9.7)
4 Fungus  (9.6)
5 Grim Reaper  (9.3)

Home - Disclaimer
Copyright © No Name 2001-2024
Page generated in: 0.09 sec.