Live data from Hacker News

A Comprehensive Super Mario Bros. Disassembly

gist.github.com

81–87 of 87 posts

Re: A Comprehensive Super Mario Bros. Disassembly

#81
post #27

There's also a Legend of Zelda disassembly, but it's not as nice: https://github.com/camthesaxman/zeldasource It's a much larger game, though. I haven't checked, but I'm assuming that a lot of the giant chunks of statically-defined data are just graphics and audio (unlike many games, LoZ stored graphics interspersed with the program code and copied data over to an in-cartridge RAM chip, instead of storing the complet…

> unlike many games, LoZ stored graphics interspersed with the program code and copied data over to an in-cartridge RAM chip, instead of storing the complete graphics data in a ROM chip This is because Zelda 1 was a port from the Famicom Disk System—no memory-mapped ROM chip to rely on, so you've got to load everything you're going to use to RAM. (Also like this: Metroid.) I believe this is why both LoZ's and Metroid…

Cool, I never dug in to see why they were that way, but those were both test cases when I built an NES emulator.

Re: A Comprehensive Super Mario Bros. Disassembly

#82

Earlier quoted context omitted.

> Would the original game have been written in assembly? And if so, would the source have looked similar to this? Yes, and probably somewhat. The programmers were Japanese, so variable names would almost certainly be different, and the assembler this code was written for is actually for the CPU in the Super Famicom/SNES, so although I'm not sure when the assembler was written, it certainly wasn't around in 1984 when…

> the assembler this code was written for is actually for the CPU in the Super Famicom/SNES Super Mario Bros is an NES game.

I know that; I played that game for years before the SNES came out, but the assembler supports the Ricoh 5A22 that was used in the SNES. The Ricoh 2A03 used in the NES is basically a subset of the SNES' processor.

I was commenting on the fact that since the assembler itself supports the 16-bit variants of the processor family, it couldn't be the same assembler that would have been used to build the original NES code, so the exact assembly language used might not be a precise match either.

Re: A Comprehensive Super Mario Bros. Disassembly

#83
post #56

Earlier quoted context omitted.

To be fair, replacing a series of ALU ops with a lookup table doesn't usually add a "data dependency" - the data dependency probably already existed, but perhaps flowed through registers rather than memory. What adding a lookup table can do is to add the load-latency to the dependency chain involving the calculation, which seems to be what you are talking about here. For an L1 hit that's usually 4 or 5 cycles, and fo…

> To be fair, replacing a series of ALU ops with a lookup table doesn't usually add a "data dependency" If it's not vectorizable, LUT result is often used for indirect jump/call (like large switch statement) or memory access (say, a histogram etc.). > What adding a lookup table can do is to add the load-latency to the dependency chain involving the calculation, which seems to be what you are talking about here. Yeah,…

> If it's not vectorizable, LUT result is often used for indirect jump/call (like large switch statement) or memory access (say, a histogram etc.).

You've lost me. The parent comments were specifically talking about using LUTs to replace calculation, and particular calculations involving branches. So basically rather than some ALU ops + possibly some branches, you use a LUT and fewer or zero ALU ops, and fewer or zero branches.

No one is talking about the context of a LUT of function pointers being used for a switch statement.

A histogram is generally a read/write table in memory and doesn't have much to do with a (usually read-only) LUT - unless I missed what you're getting at there.

> Yeah, used a bit sloppy terminology. Loads can affect performance system wide. To be a win, LUT function needs to be something pretty heavy, while LUT itself needs to be small (at least Definitely. Useful LUTs are often a few dozen bytes: the JumpMForceData one referring to about was only 8 bytes!

> Microbenchmarks often miss "system" level issues.

Definitely.

But it's like a reverse myth now: at some point (apparently?) everyone loved LUTs - but now it's popular to just dismiss any LUT use with: "yeah but a cache miss takes yyy cycles which will make a LUT terrible!" or "microbenchmarks can't capture the true cost of LUTs!".

Now the latter is certainly true, but you can certainly put reasonable bounds on the cost. The key observation is the "deeper" the miss (i.e., miss to DRAM being the deepest, ignoring swap), the less the implied frequency the LUT-using method was being called anyways. If the method is always missing to DRAM, the LUT entries are being cleared out before the next invocation (that hits the same line) which must be a "while". Conversely, when the LUT method is very hot (high of calls), the opportunity for the LUT to stay in cache is great.

You can even analyze this more formally: basically looking at the cost-benefit of every line of cache used by the LUT: if the LUT didn't use that line, what would be the benefit to the surrounding code? For any non-trivial program this gets hard to do exactly, but certainly with integration benchmarks and performance counters you can make some reasonable tests. Associativity makes everything tougher and less linear though...

> Well, isn't that where the performance wins are and what you need to do to extract maximum performance from that hot loop?

Sure, if it can be vectorized. My point there was that you were discussing the latency of L2 misses rather than the throughput, which implies that the operations on consecutive elements were dependent (otherwise it would be the throughput of 1 or 2 per cycle that would be important). So you just have to keep it apples to apples: if you assume independent ops, you can perhaps vectorize, but then the LUT comparison is a throughput one (as is the scalar alternative), but if the code is dependent and non-vectorizable, then the LUT latency more becomes important.

> A good truly parallel vector gather implementation could make (small) LUTs very interesting performance wise.

Anything that can be vectorized usually adds another factor of 4 or 8 to performance making it much harder for LUTs to come out on top, since the gather implementations on x86 anyways just use the same scalar ports and are thus still limited to 2/cycle, and without any "smarts" for identical or overlapping elements.

Sometimes you can use pshufb to do what amounts to 16 or 32 parallel lookups in a 16-element table of bytes. If your LUT can be made that small, it works great.

Re: A Comprehensive Super Mario Bros. Disassembly

#84
post #3

https://gist.github.com/1wErt3r/4048722#file-smbdis-asm-L601... I think this is where the real gems start. The biggest contribution that SMB had was the "physics engine", to retrofit a modern term. The friction, the jumping, the inertia. If you compare it with the primitive physics in Donkey Kong or Mario Brothers, you can really grasp the groundbreaking novelty that was SMB. You can change direction in mid-air, but…

Not platformers I realize, but lots of outer space games with actual acceleration/velocity calculations preceded SMB. Off the top of my head: Lunar Lander (1979), Asteroids (1979), Defender (1981), Gravitar (1982), Sinistar (1982). Just want to keep the history straight. Several of these modeled gravity as well.

Used to have a Gravitar clone for DOS. It was one of the harder games I've ever played :)

Re: A Comprehensive Super Mario Bros. Disassembly

#86

Master list of SMB glitches makes a nice companion to this. Sorry in advance if it results in anyone staying up well past their bedtime ;) https://www.mariowiki.com/List_of_Super_Mario_Bros._glitches

That'd be great if someone knowing the disassembly well enough could explain some of these glitches, for example the Minus World[0]. I guess the logic (or bug) to active this appears somewhere in the disassembly. [0] https://www.mariowiki.com/Minus_World

https://www.youtube.com/watch?v=Hv_h_R3o9r8

Re: A Comprehensive Super Mario Bros. Disassembly

#87

Earlier quoted context omitted.

Lookup tables were indeed a common technique used by games in the past.

Thank you. I wrote a long post but then I decided not to post it... Always remember to view past "Tricks" from the era they were written in. Lookup tables, in the 90's, for example, were used EVERYWHERE. People always used for sin/cos/tan functions. Fixed-point math was very common as well. Nowadays, it may seem like "magic" but it's not. It was just "the way" to get things done. I'm not downplaying the skill of the…

I was going to respond to an earlier comment of yours, decided I couldn't quite figure out what to ask, and so kept reading. But after this comment I think my question is a bit clearer: please write longer posts or start a blog with stories of the kind of work you were doing > 10 years ago. It's fascinating, and you're a good writer!
Post reply on HN