Assembly: back in time

Assembly language, in some form, dates all the way back to the first programmable computers. It started out as machine code, a series of numbers written out and somehow entered into the computer. Soon enough, though, it became a programming language in its own right. Modern assemblers have most of the same features as compilers, and many of them allow you to use macros, constant definitions, and lots of other helpful additions, but the idea is still the same: you’re working with the computer itself, by yourself.

Sacrifices

Using assembly is, in a very real sense, like going back in time. All those things we take for granted in higher languages—functions, classes, even if and while—are lost. If we want them, we have to make them ourselves. Sure, there are standards, and we can use libraries (even those written in other languages), but assembly is…primitive. It doesn’t hold your hand. It doesn’t lead you anywhere. And it’s very much working without a net.

Think of a cabin in the woods. You might go there to relax, to find yourself, to commune with nature, or to plot a string of murders. Assembly, similarly, will make you want to do all these things. (Well, maybe not the murder part. Then again, it can be awfully frustrating.) It’s a return to your roots that appeals to the self-reliant instinct of us all, a programming Walden.

Tools of the trade

Rather than dwelling on what we don’t have when working with assembly, let’s focus on what we do have. There’s not all that much, but remember this: everything we use today is built out of these simple tools.

First, types. Most high-level languages have them. Those that don’t are lying. You typically have various kinds of numbers (integer, floating-point, maybe some higher-precision deal), single characters and strings, and then whatever else the language needs. In assembly, there’s one basic type, the machine word. It’s an integer, the size of which gives us the “bits” of the processor; 64-bit processors have 64-bit words sizes, for example. (Most modern processors have additional support for floating-point numbers, but these are usually kept separate, and lower-end embedded hardware often doesn’t bother.) It’s spartan in the extreme, true, but it’s really all we need, because that same N-bit integer can be used as a number, a character, an index, or a pointer.

Next, assembly language in itself doesn’t have variables (though many assemblers offer a simulation of them), but it does have the register. These are small areas of memory in the innermost core of the processor. They’re ultrafast but truly limited, both in size (almost always a single word) and number. It was a big deal when AMD created their 64-bit extensions to the x86 architecture and added a whopping eight new registers, because that doubled what Intel had offered since the 1980s. The 6502, mainstay of the 80s home computing revolution, managed with only six, and only one of those (the A register) was truly general-purpose. (The others were two index registers—X and Y—a stack pointer, program counter, and status or “flag” register.)

Instead of dedicated control constructs like the for-loop, while-loop, or switch, assembly works on a lower level, using basic instructions. These differ for different architectures, but there are a few that are near-universal. These include arithmetic (adding and subtracting are nearly everywhere, but multiplying and dividing aren’t), bit-twiddling (shifts, AND, OR, and XOR, but also the bitwise rotate, which high-level languages ignore), loading and storing, branching or jumping (conditionally or not), some sort of “indexing” operation, and direct hardware access.

From these little blocks, great things can be accomplished, but it takes time. The tradeoff of development time for execution time is a hard one to make, and it’s almost always uneconomical, which is why assembly is so rare today. But it’s possible and sometimes necessary.

The great divide

Like any movement, assembly language has splintered into two main camps. These are divided on the way an architecture is constructed. Particularly, the sticking point is how many instructions, and of what kinds, are provided. Some like their assembly to be full of features, with instructions for everything (the CISC style). Others prefer a simpler set of building blocks (RISC). The RISC camp has all but won the battle, as most new systems are of that style, and even the CISC holdouts like x86 actually have a RISC core underlying the assembly language that we can access. Still, it’s a good historical footnote, and both sides do have their merits.

To be continued

Next time, we’ll look at an actual assembly language instruction set and architecture. (As of this writing, I haven’t decided which. I’ve narrowed it down to a few possibilities: 6502, early x86, AVR, or MIPS.) I’ll hopefully have links to online assemblers and emulators, so that readers won’t have to download anything. Then we’ll get to see what the old days were really like.

One thought on “Assembly: back in time”

Leave a Reply

Your email address will not be published. Required fields are marked *