Assembly
The lowest-level programming language, directly controlling hardware through processor instructions.
Also known as: Assembler
Created by Various (David Wheeler wrote the first assembler under Maurice Wilkes)
Assembly language is the thin layer between human-readable code and raw machine instructions. Every program, regardless of what language it’s written in, eventually becomes assembly (or the machine code it represents). Understanding assembly means understanding how computers actually work at the processor level.
History & Origins
The concept of assembly language emerged from the need to make machine code programming more manageable. In the earliest days of computing, programmers wrote programs directly in binary or numeric codes representing machine instructions. This was tedious and error-prone.
The First Assembly Notation
The history of assembly language begins before the first working assembler. In 1947, Kathleen Booth (nee Britten), working with her husband Andrew Donald Booth at Birkbeck, University of London, created the first known assembly language mnemonic notation in their work “Coding for A.R.C.” (Automatic Relay Calculator). The notation was influenced by her visit to John von Neumann and Herman Goldstine at the Institute for Advanced Study at Princeton. It was a notational system rather than an automated translation program, but it established the idea of representing machine instructions with human-readable symbols.
The Birth of Symbolic Assembly
In 1949, David Wheeler, working under Maurice Wilkes at Cambridge University, wrote the first assembler – the “Initial Orders” – for the EDSAC computer. Instead of writing raw numeric codes, programmers could use symbolic names for operations (like ADD or STORE) and labels for memory addresses. The assembler would translate these symbols into the actual machine codes.
This was revolutionary. A programmer could write:
ADD X
STORE Y
Instead of memorizing and typing numeric operation codes and absolute memory addresses.
EDSAC and the First Textbook
The EDSAC ran its first program on May 6, 1949, calculating a table of square numbers and a list of prime numbers. The Initial Orders were stored in read-only memory formed from rotary telephone selector switches and occupied just 31 words. A second version, installed in approximately August-September 1949, expanded to 41 words and added relocation facilities for subroutines.
In 1951, Wilkes, Wheeler, and Stanley Gill published The Preparation of Programs for an Electronic Digital Computer, widely recognized as the first programming textbook. It introduced the term “assembly” in a programming context, describing the process of assembling a program from component subroutines.
Symbolic Assembly and the IBM Era
Nathaniel Rochester, who joined IBM in 1948 and co-designed the IBM 701, let programmers on that machine use symbolic labels and addresses, making programs more readable and maintainable. In 1955, Stan Poley at IBM’s Watson Lab wrote SOAP (Symbolic Optimal Assembly Program) for the IBM 650. SOAP automatically optimized instruction placement on the 650’s rotating drum memory, an early example of an assembler performing optimization beyond simple translation.
The IBM System/360 brought Basic Assembly Language (BAL) in 1964, which established a mainframe assembly standard. Its descendants, culminating in IBM’s High-Level Assembler (HLASM), are still used on IBM Z mainframes.
Macro Assemblers
Macros let programmers define reusable patterns of instructions that the assembler expands before translation. IBM’s Autocoder, developed for the IBM 702/705 in the late 1950s, is credited as one of the earliest assemblers to support them, and macro facilities became a standard feature of assemblers from the 1960s onward. In 1970, Harlan Mills proposed Concept-14, a set of structured programming macros (IF/ELSE/ENDIF blocks) for the OS/360 assembler, implemented by Marvin Kessler at IBM’s Federal Systems Division.
Evolution Across Architectures
As different computer architectures emerged, each developed its own assembly language:
- IBM mainframes: Had their own assembly syntax still used in legacy systems
- PDP-11: Influenced the development of C at Bell Labs
- Intel x86: Became the dominant PC architecture from 1978 onwards
- ARM: Rose to dominate mobile devices and now challenges x86 in desktops and servers
- RISC-V: Open-source architecture gaining momentum for embedded and beyond
High-Level Languages and Cross-Assemblers
FORTRAN arrived in 1957, developed by John Backus and his team at IBM as an alternative to assembly language for the IBM 704, and it began a gradual shift away from assembly as the primary programming language. COBOL (1959) and later C (1972) expanded the domains where high-level languages could replace it. Dennis Ritchie and Ken Thompson developed C to rewrite Unix, originally implemented in PDP-7 assembly, in a portable language that kept the low-level access typical of assembly.
As microprocessors proliferated in the 1970s and 1980s, cross-assemblers became essential: tools that run on one architecture and produce code for a different target processor. Embedded targets often lacked the resources to run development tools, and cross-assembly remains the standard development model for embedded systems.
Design Philosophy
Assembly language provides a human-readable representation of exactly what the processor will do.
One-to-One Correspondence
Each assembly instruction typically maps to exactly one machine instruction. When you write mov eax, 42, the processor will execute precisely that operation - moving the value 42 into the EAX register. There’s no hidden runtime, no garbage collection, no abstraction layers.
Total Control
Assembly gives complete control over:
- Registers: The processor’s fastest storage locations
- Memory: Direct addressing, pointer arithmetic, stack manipulation
- Instructions: Access to every operation the CPU supports
- Timing: Predictable instruction timing for real-time systems
Architecture Dependence
Unlike high-level languages, assembly is tied to a specific processor architecture. x86 assembly won’t run on ARM, and ARM assembly won’t run on RISC-V. This limits portability but gives access to architecture-specific capabilities.
Programmer Responsibility
Assembly provides no type system, no automatic memory management, and no enforced calling conventions. The programmer is responsible for every register allocation, every stack frame, and every memory access.
Key Features
Labels and Symbolic Addressing
A programmer can write JMP loop_start rather than calculating and hard-coding a memory offset, and the assembler resolves the label to the correct address.
Directives and Pseudo-Instructions
All assemblers support directives, also called pseudo-instructions: commands to the assembler itself rather than the processor. These control data definition (DB, DW, DD), memory alignment, section organization (.text, .data, .bss), and conditional assembly.
Macro Systems
Modern assemblers let you define reusable instruction sequences with parameters. Macros are expanded at assembly time, providing code reuse without runtime overhead. Macro systems range from simple text substitution to Turing-complete macro languages, as in FASM.
Common File Extensions
.asm: the most common generic extension, used with MASM, NASM, FASM, TASM, and others.s: standard on Unix/Linux systems (GCC/GAS toolchain); lowercase indicates the file is assembled directly.S: on Unix/Linux, uppercase indicates the file is run through the C preprocessor before assembly.inc: include files containing macro definitions, constants, and shared declarations
Why Learn Assembly Today?
In an era of Python and JavaScript, why would anyone learn assembly?
Understanding Computer Architecture
Assembly reveals how computers actually work. Concepts like:
- The stack and function calling conventions
- Register allocation and memory hierarchy
- Instruction pipelining and optimization
- System calls and operating system interfaces
These become concrete rather than abstract when you work in assembly.
Performance Optimization
While modern compilers are excellent, they can’t always match hand-tuned assembly. Critical inner loops in:
- Video codecs (x264, AV1 encoders)
- Cryptographic libraries (OpenSSL and others)
- Mathematical libraries (OpenBLAS, GotoBLAS)
- Game engines (physics, rendering pipelines)
Often contain hand-written assembly for maximum performance.
Security and Reverse Engineering
Security professionals need assembly to:
- Analyze malware behavior
- Develop and understand exploits
- Perform binary analysis and reverse engineering
- Write secure, vulnerability-free low-level code
Embedded and Systems Programming
When working close to hardware:
- Bootloaders must be written in assembly
- Interrupt handlers often need precise assembly control
- Real-time systems require predictable timing
- Resource-constrained microcontrollers demand efficiency
Assembly Variants
Several assemblers exist for x86 architecture:
NASM (Netwide Assembler)
NASM uses Intel syntax and is popular for its clean, consistent design. It’s free, open-source, and runs on multiple platforms. This is what we use in our examples.
GAS (GNU Assembler)
Part of the GNU toolchain, GAS uses AT&T syntax by default (though it supports Intel syntax too). It’s what GCC uses internally.
MASM (Microsoft Macro Assembler)
Microsoft’s assembler for Windows development, tightly integrated with Visual Studio.
FASM (Flat Assembler)
A self-hosted assembler known for its macro capabilities and being written entirely in assembly itself.
TASM (Turbo Assembler)
Borland’s assembler, released in 1989 and popular in the DOS era. Its last version dates from approximately 1996.
Modern Assembly
Today’s assembly programming is different from the past:
Interfacing with High-Level Code
Modern assembly often exists as optimized functions called from C, C++, or Rust. You write the hot path in assembly and let the compiler handle the rest.
SIMD Programming
Modern CPUs have SIMD (Single Instruction, Multiple Data) extensions like SSE, AVX, and NEON that process multiple data elements simultaneously. These are often easiest to access through assembly or intrinsics.
Platform-Specific Optimization
While the basic operations are similar, each architecture has unique instructions. ARM (in its 32-bit mode) historically featured conditional execution of most instructions, though this was removed in ARM64/AArch64 in favor of specialized conditional instructions. x86 has CISC complexity, and RISC-V has extensibility.
Learning Assembly
To learn assembly, start with:
- A specific architecture (x86-64 is common for desktop learning)
- A good assembler (NASM is beginner-friendly)
- Understanding of basic computer architecture
- Practice with small programs before tackling complex ones
Timeline
Notable Uses & Legacy
Operating System Kernels
Critical bootloaders, interrupt handlers, and context switching code in Linux, Windows, and macOS are written in assembly.
Device Drivers
Hardware-level communication often requires assembly for precise timing and register manipulation.
Embedded Systems
Microcontrollers with limited resources frequently use assembly for maximum efficiency.
Security Research
Reverse engineering, exploit development, and malware analysis require deep assembly knowledge.
Performance-Critical Code
Video codecs, cryptographic algorithms, and game engines use hand-optimized assembly for hot paths.
Compilers
All compilers ultimately generate assembly or machine code; understanding assembly aids compiler development and debugging.
RollerCoaster Tycoon (1999)
Chris Sawyer wrote approximately 99% of RollerCoaster Tycoon in x86 assembly using MASM, with only about 1% in C for Windows and DirectX interfacing.
MenuetOS and KolibriOS
MenuetOS (by Ville Turjanmaa) and its fork KolibriOS are complete operating systems with graphical user interfaces written entirely in FASM assembly language.
Language Influence
Influenced By
Influenced
Running Today
Run examples using the official Docker image:
docker pull esolang/x86asm-nasm:latestExample usage:
docker run --rm -v $(pwd):/code -w /code esolang/x86asm-nasm x86asm-nasm hello.asmTopics Covered
Hello World in Assembly
Your first Assembly program - the classic Hello World example with Docker setup using NASM
Variables and Types in Assembly
Learn how Assembly stores and manages data using registers, memory sections, and data directives in NASM x86
Operators in Assembly
Learn arithmetic, bitwise, and comparison operators in x86 Assembly using NASM, with hands-on Docker-ready examples
Control Flow in Assembly
Learn how Assembly handles control flow using comparison instructions, conditional jumps, and loop patterns in x86 NASM
Functions in Assembly
Learn how functions work in x86 assembly with CALL/RET, the stack, calling conventions, and recursion using NASM and Docker
I/O Operations in Assembly
Perform input and output in x86 assembly using raw Linux system calls — reading stdin, writing stdout and stderr, and reading and writing files with NASM and Docker
Related Languages
Sources & Further Reading
- NASM, the Netwide Assembler (official site) Official
- NASM manual Documentation
- Using as (GNU Assembler manual) Documentation
- flat assembler message board Community
- Assembly language (Wikipedia) Reference
- EDSAC initial orders notes (Clemson University) History
- David Wheeler (IEEE Computer Society profile) History
- Nathaniel Rochester (IEEE Computer Society Computer Pioneers) History
- The Booth-Britten 1947 reports (MacTutor, St Andrews) History
- David Wheeler (IEEE Computer Society) History