DeiMOS – A Superoptimizer for the MOS 6502

(aransentin.github.io)

59 points | by Aransentin 6 hours ago ago

15 comments

  • adunk an hour ago

    That implementation of the max(A, X) operator that is described in the article is very clever. Obviously not super practical, since it needs to allocate one specific zeropage location for it to work ($8A), but that type of requirement is pretty typical for lovely tricks like this one! And the beauty here is in the trick, not whether or not there may be some practical application of it.

    But this also raises the question: is there some clever way to use the undocumented SAX instruction, which does an AND between the A and the X registers, to achieve something similar? There is and old trick to compute min and max using no branches, which is not immediately applicable (https://graphics.stanford.edu/~seander/bithacks.html#Integer...) - but maybe there is some other trick hiding in there?

  • russellsprouts 2 hours ago

    Very cool!

    I did something related in the past: https://github.com/RussellSprouts/6502-enumerator. It uses C++ templates to share an emulator implementation between z3-powered symbolic execution and actual execution. It was meant to find equivalence between random instruction sequences for peephole optimization, rather than optimizing a specific input sequence.

    Shadow instructions are very interesting and cursed. I've seen them used in very limited circumstances for NOP-slides for timing code: https://www.pagetable.com/?p=669. It would be fun to see it applied in otherwise normal code. My enumerator wouldn't support this -- it didn't execute actual 6502 instructions from bytes -- it had its own internal representation for `the first arbitrary absolute pointer` or `the second arbitrary immediate constant`. These would either be initialized with random concrete values or z3 variables.

    • Aransentin 2 hours ago

      Your project was very much something I looked into when designing this! Fun to see you commenting.

      But yes, different goals. I did look into using z3, but quickly found out that it's pretty slow compared to just checking if a test case passes when ran through the candidate program.

      • guenthert an hour ago

        Interesting project and well written. That only made me miss some links to prior art more though.

        iirc, there was a superoptimizer (I belief the term was coined and motivated in that article) in the early nineties for M68k. https://dl.acm.org/doi/pdf/10.1145/36206.36194 might have been that.

  • HarHarVeryFunny 3 hours ago

    If you assume that A * 10 isn't going to overflow, so that ASL A moves 0 into the carry flag (so no need for CLC), then instead of using the undocumented RRA opcode, you can just do:

    sta $00

    asl a

    asl a

    adc $00

    asl a

    This is also 7 bytes, but is faster since adc $00 is 3 cycles, vs rra $00 being 5 cycles.

    The A = max(A, X) example is certainly interesting, but not very useful since it loops through the code twice (very slow) and assumes that $8a is available. The much faster obvious version only adds one byte:

    stx $00

    cmp $00

    bcs done

    txa

    done:

    • Aransentin 3 hours ago

      Sure. Note that I picked those examples to demonstrate the two fairly quirky classes of things the optimizer tends to find. If the programmer has different requirements they can specify that, and it'll spit out the examples you gave (or something equivalent).

  • rbanffy 3 hours ago

    Interesting and fun read - we are well into the terrain of what was completely impossible to do back then. Now I can't wait to see a faster AppleSoft ROM ;-)

  • kstrauser 4 hours ago

    That’s incredibly clever and a fun read. Well done!

    I imagine lots of demo coders glancing back and forth between that writeup and their own carefully hand-tuned assembly.

    • Aransentin 4 hours ago

      Thank you!

      Demo coding is indeed the primary usecase for this, and the reason for why I started tinkering on it in the first place. That, and people who make homebrewed NES / C64 video games should find it fairly useful for optimizing tight loops and such.

  • potus_kushner 2 hours ago

    reminds me a bit of https://pubby.games/codegen.html just that its approach seems way more refined and useful.

    • HarHarVeryFunny 2 hours ago

      "Although NESFab performance is good, writing assembly by hand can obviously surpass it"

      These project obviously have different goals. DeiMOS is for people already writing assembler that want optimal assembler.

      NESFab is a compiler - apparently a very good one (hard to do for 6502), but nonetheless not directly competing with people hand writing assembler and looking to save every byte and/or cycle.

  • vibecoderking93 3 hours ago

    Great