[HN Gopher] Make the most of compiled C loops on the 68000
___________________________________________________________________
Make the most of compiled C loops on the 68000
Author : floitsch
Score : 64 points
Date : 2025-09-28 18:55 UTC (4 days ago)
(HTM) web link (dciabrin.net)
(TXT) w3m dump (dciabrin.net)
| dmitrygr wrote:
| Significant further gains are possible by simply unrolling the
| loop eight or 16 times to lower the overhead of the DBF per word
| written
| p_l wrote:
| The step with declaring hw registers in assembly reminds me how
| assignment of value to pointer is IIRC at best implementation
| defined, and at worst UB, and playing around with volatile saves
| you not from zealous optimizer.
|
| Arguably every hardware register should be declared that way as a
| symbol
| pjmlp wrote:
| That was a common feature on Borland and Microsoft compilers
| for MS-DOS.
| pjmlp wrote:
| With Assembly.
| robinsonb5 wrote:
| Interestingly, gcc-amigaos-gcc 6.5 uses dbra without having to
| jump through any of those contortions, as long as the
| optimisation level is set to at least -O1:
| _clear_screen: move.w #28672,3932160
| move.w #1,3932164 move.l #3932162,a0
| move.w #-13570,d1 move.w #1279,d0 .L2:
| move.w d1,(a0) dbra d0,.L2 rts
| dlundqvist wrote:
| I tried this with the old SAS/C Amiga compiler. It put
| addresses in A0 and then moved value into (A0) on next
| instruction, so the setup part was a bit more inefficient. And
| refused to use "dbra" no matter what I tried.
| odipar wrote:
| I was once into 68k so I may be rusty, but shouldn't it be
| move.w d1,(a0)+ (increment the target address after each step)?
| dlundqvist wrote:
| The hardware increments an internal pointer after each
| access. The view to that address is through value in a0.
| chris_j wrote:
| One thing that I heard from folks who do development for retro
| Atari platforms is that the 68k support in GCC has been getting
| worse as time has gone on, and it's very difficult to get the
| maintainers to accept patches to improve it, since 68k is not
| exactly widely used at this point.
|
| Specifically, I heard that the 68k backend keeps getting worse,
| whilst the front-end keeps getting better. So choosing a GCC
| version is a case of examining the tradeoffs between getting
| better AST-level optimisations from a newer version, or more
| optimised assembly language output from an earlier version.
|
| I imagine GCC 6.5 probably has a backend that makes better use
| of the 68k chip than the GCC 11.4 that ngdevkit uses (such as
| knowing when to use dbra) but is probably worse in other ways
| due to an older and less capable frontend.
| kstenerud wrote:
| SNK were the gods of the 68000. I still remember back in the day
| getting a bug report on my 68000 emulator:
|
| When playing King of Fighters, the time counter would go down to
| 0 and then wrap around to 99, effectively preventing the round
| from ending.
|
| Eventually I tracked it down to the behavior of SBCD (Subtract
| Binary Coded Decimal): Internally, the chip actually does update
| the overflow flag reliably (it's marked as undefined in the
| docs). SNK was checking the V flag and ending the round when it
| got set.
|
| https://github.com/kstenerud/Musashi/blob/master/m68k_in.c#L...
|
| SBCD was an old throwback instruction that was hardly used
| anymore, and the register variant took 6 cycles to complete (vs 4
| for binary subtraction).
|
| HOWEVER... For displaying the timer counter on-screen, they saved
| a ton of cycles with this scheme because extracting the digits
| from a BCD value is a simple shift by 4 bits (6 cycles) rather
| than a VERY expensive divide (140 cycles).
| kevin_thibedeau wrote:
| You don't need division to convert to decimal, though it will
| still be slower than using BCD operations.
| MobiusHorizons wrote:
| Oh? How do you do it? Some kind of lookup table?
| kevin_thibedeau wrote:
| https://en.wikipedia.org/wiki/Double_dabble
|
| A software implementation with masks and shifts will beat
| traditional CISC dividers.
| kstenerud wrote:
| Technically no, but they were also always fighting against
| the ROM size, trying to keep costs down. Every byte helped.
| veltas wrote:
| > Note how gcc is smart enough to detect that the expression
| ((0xc<<12) | 0xafe) is constant, so it can skip shifts and
| bitwise assembly operations and just emit the resulting immediate
| value at line 14. The same goes for the loop condition, gcc emits
| constant 1280 at line 10 in place of the multiplication 40x32. A
| classic compiler optimization called constant folding, but nice
| nonetheless.
|
| This is actually _required_ rather than an optimisation for any C
| compiler, from early on, as C semantically allows constant
| _expressions_ rather than just constants to be used for
| statically allocated sizes, etc. While the 'optimisation' is not
| guaranteed you'll see even on -O0 the constant was evaluated at
| compile-time, as it's harder to _not_ fold constant expressions
| sometimes than it is to just always fold them for the already
| required constant expression features.
| jcmeyrignac wrote:
| You can optimize further by unrolling the loop. For example:
| .L2: move.w d1,(a0) move.w d1,(a0)
| move.w d1,(a0) move.w d1,(a0) dbra d0,.L2
| rts
| allenrb wrote:
| But what about the effect on cache... oh, wait!
|
| ;-)
| commandlinefan wrote:
| This is cool, but at that point, why write it in C at all? Why
| not just hand-roll some assembler? It's targeted at a specific
| platform anyway.
___________________________________________________________________
(page generated 2025-10-02 23:01 UTC)