/Programming


Learn to develop things, get out of debt, find answers and take part in coding and programming challenges.


Members: 10
Join


Moderated by: mozzapp
up
4
up
Manon_code 1786043737 [Programming] 1 comments
Every time someone opens an ARM disassembly for the first time after years of dealing only with x86, the same doubt hits: why does this look so much simpler? And right after that comes the suspicion, because things that look too simple usually mean you're missing something. It's not that you're missing anything, it's a genuine difference in philosophy, and that's worth unpacking slowly, because the "simplicity" of ARM comes at a cost, and the "messiness" of x86 exists for a reason that still, to this day, keeps half the planet's server infrastructure running. The root of it all is that x86 lets you add a value straight from memory to a register in a single instruction, something like add eax, [ebx]. Sounds like a small convenience until you realize that under the hood, this one instruction is doing a load, an addition, and potentially dealing with memory alignment all at once, and the processor has to decode something far more elaborate as a result. ARM doesn't allow that. In ARM, if you want to add something sitting in memory, you first load that value into a register with an LDR, do the math, and if you need to write it back, you use a STR. No arithmetic instruction in ARM ever touches memory directly, full stop. That's the load/store design, and it's the one decision that explains pretty much every other difference that actually matters in practice. A direct consequence of this is the number of registers. Since every operation in ARM depends on values already sitting in registers, it makes sense the architecture gives you plenty of room for that, and ARM64 offers 31 general-purpose registers. x86, even in its 64-bit form with the extension that brought registers like r8 through r15, still carries some of the weight of having been born with really only about 4 usable registers, and that shows up in generated assembly: x86 code tends to reuse the same registers over and over inside a function, pushing and pulling things off the stack, while the same code compiled for ARM can often keep almost everything in registers from start to finish. It's not that ARM is "better written," it just has more drawers to work with. Instruction size is another thing that catches people off guard. x86 has variable-length instructions, anywhere from 1 byte to a mess of up to 15 bytes with every possible prefix stacked on, which is great for code density but terrible for predictability: the processor doesn't know where one instruction ends without decoding it first. ARM fixed everything at 4 bytes per instruction (at least in A64, since the Thumb side of 32-bit ARM is a whole other story). In practice this means navigating an ARM disassembly is mechanical in a good, boring way, every line takes up exactly the same space, and you can jump straight to the address you want without counting bytes along the way. Anyone who's tried manual reverse engineering on x86 knows the pain of miscalculating an offset because of some instruction prefix that slipped by unnoticed. Then there's the flags situation, which completely changes how you read a comparison in code. In x86, most arithmetic instructions already touch the flags as a side effect, so a sub followed by a conditional jump is often enough on its own. In ARM you have to be explicit about it: either use a dedicated CMP instruction, or add the S suffix to a normal instruction to say "yes, I actually want this to affect the flags this time." That makes ARM code a bit more verbose in this specific spot, but also easier to audit, since it's obvious exactly where a comparison is meant to happen. Calling conventions are usually where people debugging real code get stuck first, more than anything theoretical. In x86-64 on Linux, the first arguments to a function go in rdi, rsi, rdx, rcx, r8, r9, in that order, with the rest on the stack. ARM64 follows similar logic but the registers shift to x0 through x7, and anyone who's debugged both knows the first instinct when staring at a crash dump is always to check the wrong register if you just switched architectures. It's worth opening gdb or lldb on some function and just watching the arguments arrive, that teaches more than any table ever will. If you compile the same simple C function, say adding two integers from an array, on both architectures, the difference jumps out fast. The x86 code will have one instruction touching memory in the middle of the math. The ARM code will have an LDR beforehand, the operation on its own after that, and if needed, a STR at the end. It's not necessarily more lines, but it's a separation of intent that makes optimization easier and, honestly, makes it easier to follow exactly what the processor is doing step by step. And why does any of this matter outside academic circles? Because that separation between load and store makes the pipeline more predictable, which helps quite a bit with power efficiency, and it's one of the reasons, among several others tied to microarchitecture design, why ARM dominates mobile and has been gaining ground on desktop since Apple Silicon showed up. x86 still rules servers and traditional desktops far more because of decades of optimized software and ecosystem lock-in than because of any inherent advantage in the instruction set itself. One trap that catches even experienced people: AT&T versus Intel syntax on x86. Tools like gdb show AT&T by default, where operands are reversed (source, destination) and every register gets a % in front of it, while objdump with the right flag or Ghidra usually show Intel syntax, closer to what you'd see in a textbook. Mixing the two up while reading a crash is a guaranteed way to lose half an hour wondering why the logic "doesn't add up." Memory alignment deserves a mention too, because ARM has historically been much stricter about accessing unaligned data, something x86 tolerates almost transparently. Code that runs fine on x86 can simply crash on ARM if it's doing careless pointer casting, and that catches a lot of people porting older code for the first time. In the end, understanding these differences isn't about memorizing instruction mnemonics, it's about understanding why the compiler made that specific choice when you look at the generated assembly. Which leaves the question: is it even worth still thinking of these two architectures as "competitors," or have they already become just different tools for different contexts, each one carrying the scars of design decisions that made sense decades ago?
up
0
up
mozzapp 1786044946
Honest question for people who've shipped code on both: now that Apple Silicon proved ARM can win outside mobile, is x86 still around because it's actually better at anything specific, or purely because of 40 years of software nobody wants to rebuild?

A social news and discussion community