A Friendly Introduction to Assembly for High-Level Programmers
shikaan.github.io
shikaan.github.io
It's hopelessly outdated as an architecture, but the instruction set is way more suited for teaching (and you should look at the classic procedure from Harris and Harris where they actually design a pipelined MIPS processor component by component to handle the instruction set).
I believe it has already replaced MIPS for teaching assembly language at many universities. Another one I've seen a lot is the 8-bit MCU Intel 8051.
In don't think it makes sense to learn a "clean" language first. Always learn a useful language and never learn a language that has no organic utility as step 1 of learning a useful one.
Unless the person taking this advice will program MIPS devices, then go ahead.
The analogy is a little off because most people do not much need to be linguists.
But programmers almost always benefit from being a linguist that can pick up any language and do some job and then the next job might be any other language.
The abstract language allows you to see the difference between the universal principles and the arbitrary quirks.
You don't think it's worth the abstract stage it to know that? I do.
Even though I'm not primarily a developer. This is just as an ordinary mere user of computers. Just the plain utility of being even basically literate and functional and do a little coding or modifying in any language that the current project happens to use is extremely valuable in whatever I want to do at any given time.
Not to mention the entirely other obvious thing that you almost always learn a simple version of any new thing rather than starting immediately with the most advanced and complex version.
No, this reasoning does not hold up to scrutiny.
Of course, some people might consider modern hardware advances to be the fundamentals. Like if you aren't learning about simd and so on, taking into account caches and multi-cores, what's even the point, sort of opinions. I can almost get behind that.
My biased opinion though is that if you want to learn beyond a casual level, you should definitely be learning something that is running on actual hardware, not some sort of emulation layer. x86 then comes back into prominence since most PCs are that. However in the context of a captive audience like students in a classroom, the teacher can decide on something less insane, more fun than things like figuring out how to take an -O0 compiled C file and optimize a loop (ignore -O2 will do it better), and more showcasing a "need" because unlike your x86 class assignment, you can't just trivially do it in a higher level language, and you can't just swap in an x86 chip to a PCB. My first introduction to real assembly in college was with a little PIC microcontroller to control a robot car. I knew a bit of x86 by then, just enough to compare, and it was such a breath of fresh air just to use something not x86. It could have been ARM (we later used an ARM chip to program an RTOS and do other embedded systems relevant stuff), or anything really, the important thing is it was real. In the quest for teaching a "simpler assembly" I know some schools have done things I consider absurd like having students learn some other architecture but can only run code in some provided emulator on Windows, not a real chip with real peripherals you need to write code to talk to.
I still feel completely intimidated and out of my depth with modern assembly. I have no idea where to start. There are just so many instructions, conventions, registers with strange names and conventions that include each other, etc. Add to that that many of the examples you see are trying to beat the compiler, so doing clever/wide stuff. A comprehensive, modern tutorial that starts simple but goes more or less the whole way would be welcome.
I'm not surprised. But keep in mind that "create something that works on a modern CPU and beats the compiler's output" is not the only possible goal of learning assembly programming. I would say it's not even the usual goal, and hasn't been for a long time.
One thing that tripped me up a bit is that on x86 standard practice is using "taking address" lea operation combined with weird addressing mode to do simple arithmetics. I had to ask ChatGPT wtf is that.
In the process I learned that 686 assembly is a very different beast and they don't play nicely with each other (or at all).
I apparently also need to internalize harder that I should just ask llms about everything :-)
[0] IBM System/370 Principles of Operation (the "big yellow book")
http://bitsavers.trailing-edge.com/pdf/ibm/370/princOps/GA22...
Also, the gp register- don't know of any other arch where you need to set something like that up to access global variables. It's another layer of indirection which makes the assembly code harder to read (especially PIC code that works off of the value of the t9 register)
10082410 9de3bf98 save %sp, 0xffffff98, %sp
10082414 90102001 mov 1, %o0
10082418 92100018 mov %i0, %o1
1008241c 94100019 mov %i1, %o2
10082420 7fffff17 call dirList
10082424 96102000 clr %o3
10082428 81c7e008 ret
1008242c 91e80008 restore %o0No, several other architectures of that period have delay slots (80s/90s), and modern VLIWs/DSPs still often have them. Sometimes the slots longer than a single instruction.
Emu86.assembler.virtual_machine > MIPSMachine(VirtualMachine) , RISCVMachine(VirtualMachine) , IntelMachine(VirtualMachine) https://github.com/gcallah/Emu86/blob/b48725898f37dede3ab254...
Learn x in y minutes > "Where X=MIPS Assembly" https://learnxinyminutes.com/docs/mips/
From a programmer POV, RISC-V is not unlike MIPS, but much easier.
A very minor grammar thing, here:
In our example, the mnemonic is mov, which stands for move, and the operands are rax and rbx. This instruction in plain English would read: move the content of rbx in rax.
I believe the last part would be better as "... content of rbx to rax", i.e. "to" instead of "in". I'm not a native speaker, though.
mov rax rbx
Into rax, copy the contents of rbx.move Fiji belongings
- "Show HN: Tetris, but the blocks are ARM instructions that execute in the browser" (2023) https://news.ycombinator.com/item?id=37086102 ; the emu86 jupyter kernel supports X86, RISC, MIPS, and WASM; and the iarm jupyter kernel supports ARMv6
Some condition codes are different depending on signedness of the numbers being used. "Greater"/"Less"/"Overflow" are for signed, "Above"/"Below"/"Carry" for unsigned. The sign flag by itself is not what you would test when comparing two numbers, since the subtraction done by CMP might have overflowed - that's why the condition for "Less" is defined as "Sign XOR Overflow".
There are various arguments for always using signed types in C, but none of that applies to assembly, and unsigned is more appropriate in most cases. So maybe these conditions should be introduced first?
Readers might be confused why it is called EFLAGS, or about the register names, so maybe a little history should be included: registers were originally 16 bits, then "E"xtended to 32 bits, and later "R" was used to indicate 64 bits. AH/CH/DH/BH correspond to the high byte of the 16 bit registers AX/CX/DX/BX, not the extended ones. These aren't used much anymore.
Good tutorial nonetheless!
So many others don't even mention that official documentation by Intel / AMD exists. Instead it's mostly "here's what code GCC/Clang generates for this C program", in that horrid AT&T syntax, and links to one of several third-party reference sites containing nothing but giant dense tables of mnemonics, opcodes and flags. No wonder when people reading those come away convinced that it's impossible to actually understand this stuff.
The second article is still shaping up, but I felt like publishing it to get some early feedback. What you mention are a couple of the reasons.
At this point, I find it too dense (and it was even more so in first drafts) hence missing information. Maybe I should cut some parts, like the rip intro, and accommodate for more details about flags and conditions.
This is good signal for me. Thanks again for taking the time
The focus of the series is in on accessibility. Sometimes it comes at the expense of telling the whole story: I was even on the fence on introducing the .bss block, honestly.
The article mentions initialized variables, but maybe this warrants a footnote.
Thanks for the feedback and for reading through the article. I appreciate a lot :)
Teaching wrong things does not make the article better accessible, but more confusing.
Thanks once again for the feedback :)
(He is a an engaging tech author - I have never not loved one of his books)
I know about Daniel Lemire / lemire.me
Anybody / anything else you'd recommend?
I'm not an expert with M1 assembly, just a tinkerer, so I never felt wise enough to write a blog or record videos. I wish I could have found some for myself!
So, here we go. Blog post as a comment.
Assembly and mnemonics are there to be human readable representations of a fictitious thing called bits which are always “stored somewhere”. Now, bits don’t actually exist, nor does their storage, but voltage differences, magnetic/electric fields and currents do. There are no intermediate layers in hardware that translate your “program” into this pattern - your “files” already exist in as this pattern somewhere because again, it’s voltages, fields, and currents, and that’s all there is. This means all those abstractions and OSI layers don’t exist - all that stuff is make-believe. Files and file systems don’t exist. Hence, assembly doesn’t exist.
I’ll skip over a thing called microcode and microarchitecture. Both of these deal with taking your bit pattern and re-arranging it into a different pattern that the exact processor you’re using is optimized/carefully designed for and doing other mean things to your program. In general you wont encounter this when doing assembly or any other high level language, but certain processors do allow for some play at program-level. You can configure the processor exactly how you want it to function and this is packaged with your program code.
Now, when we talk about data buses, instruction buses, “cache lines”, and all these things what we are really talking about are parallel traces on a circuit board on which one can sample, usually though not always, a voltage difference on each trace and see if it’s “high” or “low” (your “bit”). The number of these traces usually corresponds to the number of bits. Hence, an 8-bit, 16-bit, 9-bit and a 32-bit processor again, usually though not always will have physical lines on a circuit board corresponding to this number. Hence, your assembly instruction is an English term for an ordering of these lines at a point in time.
Here’s the OG 8086 processor chip. You can see physically it has AD0 to AD15 pins, hence 16 “bits” of voltage levels that are “high” or “low”: https://www.geeksforgeeks.org/pin-diagram-8086-microprocesso...
So your assembly instruction is an arrangement of the voltage levels for these pins. The processor samples it, and proceeds to raise or lower other pins it has. And it goes round and round like this until it overheats and dies on you after some years of abuse.
You write your program, you store this in “memory” as an ordered arrangement of voltages/fields. A “controller” reads this and presents the processor at any point in time with the voltage levels, directly hooked up to its pins. The processor goes ahead and does it’s thing. There’s very little rocket surgery to it.
Processors of course are not usually this simple. They are nowadays structured do many steps at once, often times abusing a clock (again, voltage differences) and decreasing sizes of chips to go faster and faster.
One point to mention here is, you can probably now see that the data pins of something like an 8086 can literally be hooked up to anything - there’s no separation between “code” and “data”. By now you should understand that these, too, are make-believe. A pattern of signals is a pattern of signals no matter where it’s coming from. Although some processors will tell you otherwise and they get fancy with a thing called memory maps. They too, don’t exist.
So, armed with this hopefully demystifies for you a bit the why of assembly. It’s a convenient, human readable form of instructions that a processor can execute. It is a fairly banal and trivial thing in the end. The real guts of it is knowing how the processor will execute it, what it will do before and after. Assembly as a language can be picked up relatively quickly.
It should be noted that, should you choose to, you can open up the manual of your favourite processor, and chances are highly likely you’ll get a listing of not only mnemonics (for your reading pleasure), but the aforementioned voltage levels and functions of each of the of pins, as well as the “bit pattern” for each instruction. Some manuals will, if you’re really lucky, tell you how to design your circuit board for the processor to function properly, how far your memory is allowed to physically be from the processor, and what other things you must keep in mind.
Clear as mud?
(epilogue: if you’ve found this info tickles your fancy, you should definitely look into electronics and start building circuits, both analog and digital ones, get comfortable with reading data sheets and understanding how processors interact with the peripherals around them to perform their duties. This is an immense field of lots of things which would fascinate you and are truly science fiction.)