> Anecdotally when looking at the assembly for Intel or Arm, they are often very close.

First: Modern ARM chips (i.e. what you will typically find in a mobile phone) support three instruction sets: A32, T32 (Thumb-2 on modern ARM chips) and A64 (if you don't know what they mean, google it up). So it hardly makes sense to talk of "ARM assembly" if you don't specify which instruction set you mean. I know there is UAL, which in some sense unifies the instructions of A32 and T32 on assembly level.

There are very central differences that you will observe when looking at the disassembly of all ARM instruction sets vs. x86-32 or x86-64.

- Typical x86 ALU instructions have 2 operands. Typical ARM ALU instruction will have 3. This is very typical for CISC vs. RISC instruction sets.

Side note: A compiler often does register allocation by graph coloring. If 3-operand instructions are used this is an NP-hard problem. On the other hand if 2-operand instructions are used, we obtain a chordal graph, for which the graph coloring problem can be solved in polynomial time: https://en.wikipedia.org/w/index.php?title=Chordal_graph&old...

- x86 ALU instructions also accept a memory both as source and destination (side note: if we add a register to the value at a memory address, on modern x86 cores this is mapped to 3 mu ops: load value, add, storeobly value). On the other hand (typical for RISC) ARM is a load-store architecture. So ALU instructions don't accept memory address contents as operands - instead one has to load the content of the address into a register first, do the ALU instruction and use a store instruction to store the result in memory.

Side note: On x86 "{alu_instruction} r/m, r" (e.g. add [edx], eax) is atomic with respect to interrupts, e.g. when an interrupt occurs either this instruction has been executed completely or not at all. You want to make it also atomic with respect to code executing on other cores? Just add a LOCK prefix.

- Even in 32 bit mode ARM already offers many registers (16 GPRs) and in A64 even 32. x86-32 only offers 8 and x86-64 16. Having more and orthogonal registers is typical for RISC vs. CISC. Thus ARM's call convention uses registers for passing the parameters (again very typical for RISC) and uses a link register for passing the return address. x86-32 instead typically passes return address and parameters on the stack (I know that under 32 bit Windows there also exists the __fastcall call convention that uses registers for passing parameters, too. But this is an exception). But admitted: under x86-64 on the Linux and Windows call convention they also use registers for passing parameters.

- At least A32 and T32 cannot directly call functions that are too far away from the current instruction. So the linker has to insert veneers, which make this possible. One of course will not find veneers on x86.

- It is not easy to access memory addresses relative to the instruction pointer on x86-32 (under x86-64 it is easily possible), while on ARM it is. So ARM assembly code will often contain small const variable values next to the function. One hardly sees such code on x86-32 (though on x86 it is not really necessary, since there are instructions that put some arbitrary constant into a register; A32 and T32 on the other hand allow only to encode some kind of constants, bases on a rather complicated scheme that I will not explain here; so this trick is rather helpful for writing ARM code).

TLDR: Better look again at the disassembly carefully.