Computer architectures


Recommended reading:
Jim Ledin: Modern Computer Architecture and Organization.
Birmingham – Mumbai: Packt, 2020.

Wikipedia, selected entries. (2025-09-28)



Computer architecture - A brief overview

Computers have become an integral part of our daily lives. They power everything from smartphones to hospital systems and have shaped society to such an extent that many people simply couldn't live without the hardware and software that defines the digital world.

Despite this, the majority of people still have no idea how computers work and the role of hardware and software in powering the modern technologies we use today.

Behind the sleek* screens and intricate interfaces, computer architecture forms the fundamental components and processes that make our computers tick. (Stewart 2005)

Computer architecture (CA) is the structure of a computer system made from component parts. At the highest level, the computer can be considered as a black box, while at the lowest level as a complex network of physical components like combinational and sequential circuits and logic gates.

At each level, CA describes the internal organization of a computer in an abstract way that ignores details of the implementation at the lower level. At the highest level, CA defines the capabilities of the computer (from the user's viewpoint) and its programming model (from the programmer's viewpoint).

CA is the science and art of designing computers by defining the functional behavior and organization of hardware components like the CPU, memory, storage and I/O devices etc., including how they interact. CA establishes*
– the Instruction Set Architecture (ISA);
– the Microarchitecture;
– the Hardware System Architecture (HSA);
– the Macroarchitecture.

Key Components of Computer Architecture

Why Computer Architecture Matters

Vocabulary:

Pronunciation symbols

establish [i staeblish] = to build or bring into being sth on a stable basis (Webster 2009)
syn/rel: ground, base, be the basis for

Scalability establishes the ability of the system to handle a growing amount of workload.

blueprint [blu:print] = a design plan or other technical drawing (e.g. a system diagram, a data flow diagram etc.)

contour [kontuö(r)] = the outline of a figure or body; the edge or line that defines or bounds a shape or object (Webster 2009)

sleek [sli:k] = having a smooth attractive shape (Longman 2009); finely contoured; streamlined (Webster 2009)

a sleek computer screen

References:

AI about "computer architecture" Google Search. (2025-09-11)

Stewart, Ellis 2025. What is Computer Architecture? Definition, Types, Structure.
https://em360tech.com/tech-articles/what-computer-architecture-definition-types-structure (2025-09-11)

Illingworth, Valerie – Pyle, Ian 1996-1997. A Dictionary of Computing. Oxford – New York etc.: Oxford University Press.

Ledin, Jim 2020. Modern Computer Architecture and Organization. Birmingham – Mumbai: Packt.

Stallings, William 2018. Operating Systems. Internals and Design Principles. Edinburgh: Pearson, 2018.

Wikipedia entries: Computer Architecture etc.



Brief overview of computer system hardware (cf. Stallings 2018: 30-32)

In general, a computer consists of a processor, a main memory, and several input-output (I/O) components.

Von Neumann architecture
The Von Neumann architecture

Block diagram of a computer
Block diagram of a computer with uniprocessor CPU
(black lines indicate the flow of control signals, whereas red lines indicate the flow of processor instructions, address information and data. Arrows indicate the direction of flow)

System bus architecture
Single system bus architecture

Computer Components: Top-Level View

The figure above illustrates the logic of the operation of the system bus. The CPU contains some (internal) registers to support data exchange among the CPU, the main memory and the I/O module. These registers and their function are as follows:



Execution of instructions (cf. Stallings 2018: 32-35)

A program to be executed by a processor consists of a sequence of machine-level instructions stored in the memory. In its simplest form, the processing of instructions consists of two basic steps:
– first, the processor reads (or fetches) the instructions from the memory one at a time, and
– second, the processor executes each instruction.
The execution of a program is a repeating process (a cycle or loop) of these two steps: the instruction fetch and the instruction execution. (Note that instruction execution may involve several operations and depends on the nature of the instruction.)

The figure below illustrates the instruction cycle:

Basic Instruction Cycle

At the beginning of each instruction cycle, the processor fetches an instruction from memory. In this respect, the program counter (PC) register is of utmost importance: the PC holds the address of the next instruction to be fetched. After the instruction has been fetched, the processor increments the value of the PC so that it will hold the address of the next instruction in the sequence of instructions (i.e. in the program which is currently being executed).

The fetched instruction is loaded into the instruction register (IR). An instruction is normally made up of a combination of an operation code and the specification of the operands that present or refer to the data upon which the operation is to be performed. The operation code of the instruction contains bits that specify the action the processor is to take. The processor (or more specifically, the control unit of the processor) interprets the instruction and performs the required action. In general, these actions fall into four categories:

The execution of an instruction may involve a certain combination of these actions.



An example of the operation of the fetch-execute cycle (cf. Stallings 2018: 33-35)

Let the memory of a virtual machine be organized with 16-bit length (i.e. word-length) memory cells. Each instruction consists of a 4-bit operation code (opcode) and a 12-bit operand. Note that if the operand contains an address, this allows to directly address a maximum of 212=4096 memory cells.

Instruction format (example)

We shall use four hexadecimal digits to represent the 16-bit (one-word) content of the registers, memory addresses and the content of memory cells. (Note that for the 12-bit long addresses three hexadecimal digits would be enough.) Similarly, we shall use one hexadecimal digit to represent the opcode of each instruction.

In the example we want to add two whole numbers represented by two's complement code. We will use one general-purpose register (the accumulator, AC) and three instructions as follows:

We assume that the first instruction to be performed is located at the memory address 300 followed sequentially by the further instructions of the program (located at the addresses 301, 302 etc., respectively). Furthermore, we assume that the data that the program manipulates are stored in the memory locations between addresses 940 and 941.

Memory content
Address Content
(instructions)
0 3 0 0
1 9 4 0
0 3 0 1
5 9 4 1
0 3 0 2
2 9 4 1
(data)
0 9 4 0
0 0 0 3
0 9 4 1
0 0 0 2

Now let's see how the operation is performed in three fetch-execute cycle.

– In the example we analyze in detail the operation of the fetch-execute cycle. Since the initial value of the program or instruction counter register (PC or IP) is set to location 300, in the first cycle the processor will fetch the instruction at the memory location 300 and then immediately increments the value of PC. On the succeeding instruction cycles, the CPU will fetch instructions from locations 301, 302, and so on. (Note, however, that the sequential execution of instructions can be altered at any time by a certain control instruction.)

– In each cycle the fetched instruction is always loaded into the instruction register (IR). The operation code (opcode) of the instruction will specify the necessary action that the processor is to take. After separating the opcode and the operand, the processor (actually, the control unit) interprets the opcode of the instruction and sends control signals to the appropriate units to perform the required action.


1st. cycle
Storage unit Value Comment
Fetch stage
PC
0 3 0 0
fetch the instruction from M(300)
M(300)
1 9 4 0
load the content of M(300) into IR
IR
1 9 4 0
interpret the instruction
  • opcode=1: move memory data into AC
  • operand=940: the data is located at M(940)
PC
0 3 0 1
increment the value of PC with 1
Execute stage: AC←M(940) or MOV AC,M(0940)
M(940)
0 0 0 3
load the content of M(940) into AC
AC
0 0 0 3
store the content of M(940) in AC

2nd. cycle
Storage unit Value Comment
Fetch stage
PC
0 3 0 1
fetch the instruction from M(301)
M(301)
5 9 4 1
load the content of M(300) into IR
IR
5 9 4 1
interpret the instruction
  • opcode=5: add memory data to AC
  • operand=941: the data to be added is located at M(941)
PC
0 3 0 2
increment the value of PC with 1
Execute stage: AC←AC+M(941) or ADD M(0941),AC
AC
0 0 0 3
add the content of M(941) to AC
M(941)
0 0 0 2
AC
0 0 0 5
store the result of the addition in AC

3rd. cycle
Storage unit Value Comment
Fetch stage
PC
0 3 0 2
fetch the instruction from M(302)
M(302)
2 9 4 1
load the content of M(302) into IR
IR
2 9 4 1
interpret the instruction
  • opcode=2: move the content of AC into a memory cell
  • operand=941: the memory cell is located at M(941)
PC
0 3 0 3
increment the value of PC with 1
Execute stage: M(941)←AC or MOV M(0941),AC
M(941)
0 0 0 2
move the content of AC into M(941)
AC
0 0 0 5
M(941)
0 0 0 5
store the content of AC in M(941)

In this example three instruction cycles were needed, each consisting of a fetch stage and an execute stage. As a result, we added the contents of the memory location 940 to the contents of the memory location 941, and then stored the sum at the memory location 941.

The following figure summarizes the process.

Example of Program Execution



Implementation of the above example in Windows
II.1.1. Create and compile C files

In the following, first we write C programs, compile and run them. Then we compile the C programs to assembly programs, analyze their structure, modify them, and finally we compile and run the modified assembly programs.

Table of contents:

  • Printing "Hello World!" (hello.c)
  • Adding 3+2 (simple.c)
    • Creating a batch file to display ERRORLEVEL (err.bat)
    • Assembly version of 'simple.c' (simple.s)
    • Explanation of the stack (push, pop)
    • Explanation of the stack frame
    • Assembly version of 'simple.c' with quadword-length operands (simplex.s)
  • Adding 3+2 with a function (simplef.c)
    • Assembly version of 'simplef.c' (simplef.s)
    • Explanation of function calls (call, ret)
    • Simulation of the execution of the 'simplef.s' assembly program
  • Adding and printing 3+2 (example.c)
    • Assembly version of 'example.c' with quadword-length operands (examplex.s)

Printing "Hello World!" (hello.c)

Open a new 'cmd' window in the c:\temp\ directory and set the default path running the 'setpath' command (only once). Using the notepad hello.c command, create a new file named 'hello.c' with the following content:

#include <stdio.h>

int main() {
 printf("Hello world!\n");
 return 0;
 }

Compile, link and run the C program as follows:

Compile and run hello.c using GCC

Adding 3+2 (simple.c)

Now let us create another simple C program which implements the former example adding two integers together. Open a new 'cmd' window in the c:\temp\ directory, run the notepad simple.c command, and create a new file named 'simple.c' with the following content:

#include <stdio.h>

int main() {
 int a=3;
 int b=2;
 b=a+b;
 return 0;
 }

Compile, link and run the C program as follows:

Compile and run simple.c using GCC

Note that in the 'cmd' window, we can display the returned value of the 'simple.exe' program using the echo %ERRORLEVEL% command.

For the sake of simplicity, let us create a batch file named 'err.bat' using the notepad err.bat command. It is to contain those two lines:

@echo off
echo %ERRORLEVEL%

With that we created a new command called err which will easily display, if entered, the actual value of the ERRORLEVEL environment variable in the 'cmd' window.

It will be instructive for later considerations that using the GCC compiler we can generate easily the assembly code of the 'simple.c' program (as well as any other C programs). For that purpose, we should enter the gcc simple.c -S -o simple.s command in the 'cmd' window.

Compile the simple.c program into assembly code using GCC

The generated assembly program is as follows:

	.file	"simple.c"
	.text
	.def	__main;
		.scl	2;
		.type	32;
	.endef
	.globl	main
	.def	main;
		.scl	2;
		.type	32;
	.endef
	.seh_proc	main
main:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$48, %rsp
	.seh_stackalloc	48
	.seh_endprologue
	call	__main
	movl	$3, -4(%rbp)
	movl	$2, -8(%rbp)
	movl	-4(%rbp), %eax
	addl	%eax, -8(%rbp)
	movl	$0, %eax
	addq	$48, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (GNU) 13.2.0"

The most important pointers are the quadword-length stack pointer (%rsp) and basic pointer (%rbp). They can be declared in C something like this:

In the assembly language the local variables are represented as pointers. For example, the doubleword length local variables 'a' and 'b' can be declared in C something like this:

where the addresses and values of the local variables can be set relatively to the content of the basic ponter:

Now delete those gray lines from the generated assembly program that are not important to us (because they contain metainformation for the compiler etc.). In addition, move the sum of the operands to the 32-bit accumulator register %eax before the program ends so that we can see the result after the program has run (e.g. by using the previously created 'err.bat' program in the CMD window to display the %ERRORLEVEL% system variable).

The resulting assembly program is as follows (simple.s):

	.text
	.globl	main
main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$48, %rsp
	movl	$3, -4(%rbp)
	movl	$2, -8(%rbp)
	movl	-4(%rbp), %eax
	addl	%eax, -8(%rbp)
	movl	-8(%rbp), %eax
	/* instead of movl $0, %eax */
	addq	$48, %rsp
	popq	%rbp
	ret

The explanation of some important parts of the assembly code:


The pushq %rbp instruction moves (stores, saves) the content of the %rbp basic pointer register to the top of the stack.

The stack is a dedicated and designated part of the memory which can store (and retrieve) data according to the current needs of the programs. In this respect, the push and pop instructions are of most importance for
– adding data to the top of the stack (push), and
– removing, as well as retrieving, data from the top of the stack (pop).

The stack consists of a sequence of memory cells to store (and retrieve) data:
Push and pop data to and from the top of the stack
Within the stack, the memory cell with the smallest address is called the top of the stack. The actual address of the top of the stack is always stored in the %rsp stack pointer register. So the function of the %rsp stack pointer register is to point to the top of the stack.

The programs use a dedicated register called stack pointer (%rsp) that always points to the top of the stack. It contains the address of the last data item that has been pushed.

In order to use a stack
– when a data item is pushed into the stack first the stack pointer is decreased by the size of the data (e.g. by subtracting 8 from the actual value of the stack pointer for a quadword), and then the data is stored at that address;
– when a data item is popped from the stack first the data that is located at the top of the stack is retrieved and stored in the operand of the 'pop' instruction (e.g. in a specified register), and then the stack pointer is increased by the size of the data (e.g. by adding 8 to the actual value of the stack pointer for a quadword).

The diagram below illustrates the push and pop operations:
Illustration of the push and pop operations

For example,
(1) pushing the content of the quadword-length (i.e. 8 bytes long) %rbp basic pointer to the top of the stack corresponds to the execution of two instructions:
    subq $8, %rsp
    movq %rbp, 0(%rsp)
(2) popping the data item from the top of the stack to the the quadword-length (i.e. 8 bytes long) %rbp basic pointer corresponds to the execution of two instructions:
    movq 0(%rsp), %rbp
    addq $8, %rsp

Let us simulate the operation of the stack, supposing that in a virtual machine the initial value of %rsp is 1020, and the stack contains random values (generated by the simulation). To use the stack, click on either the 'PUSH' or the 'POP' cell.
Note that the size of the stack is limited is this simulation: the minimum value of the stack pointer is 1000, and the maximum value is 1036.

PUSH
POP
stack pointer address 4-byte long memory cells
0996 (out of limit, can't be used)
1000 ?
1004 ?
1008 ?
1012 ?
1016 ?
%rsp= 1020
1024
1028
1032
1036
1040 (out of limit, can't be used)

The next two lines of the 'main' section creates a stack frame.

The stack frame is a dedicated and designated part of the stack which can store local data according to the needs of the currently running programs. Using the terminology of high level programming languages, the stack frame allocates space within the stack for the local variables and parameters of the functions defined in a program (including the 'main' function which is always present).

Each function of the running program has a unique stack frame. The memory cell with the smallest address is called the top of the stack frame. The address of the top of the stack frame is stored in the %rsp stack pointer register. The memory cell with the highest address is called the bottom of the stack frame. The address of the bottom of the stack frame is stored in the %rbp basic pointer register.

The basic pointer (%rbp) register has a special purpose: it points to the bottom of the stack frame of the current function, so local variables can be accessed relative to its value.

The created stack frame of the simple.s assembly program is 48 bytes long which is enough to dynamically allocate 12 doubleword length (i.e. 12*4 bytes long) memory space for local variables and parameters as follows:


The other instructions of the simple.s assembly program are as follows.


After such considerations, we can easily create the 'simplex.s' assembly program which contains quadword length operands, and returns the sum of the addition (as an %ERRORLEVEL% value):

.text
.globl	main

main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$48, %rsp
	movq	$3, -8(%rbp)	# local variable a
	movq	$2, -16(%rbp)	# local variable b
	movq	-8(%rbp), %rax
	addq	%rax, -16(%rbp)	# the sum of a and b
	movq	-16(%rbp), %rax	# %ERRORLEVEL% value
	addq	$48, %rsp
	popq	%rbp
	ret

Note that in a C program, using quadword-length integer data corresponds to the 'long long int' data type in GCC. So if we wrote a C program with that type of variables, the corresponding assembly program would seem like the program above.

Compile, link and run the assemply program as follows:

Compile and run simplex.s using GCC



Implementation of other examples in Windows
II.1.2. Create and compile C files (continued)

Adding 3+2 with a function (simplef.c)

The aim of the 'simple.c' program can also be implemented using a function named 'sum' which adds two integers together. Open a new 'cmd' window in the c:\temp\ directory, run the notepad simplef.c command, and create a new file named 'simplef.c' with the following content:

#include <stdio.h>

int sum(int x,int y) {
 int temp;
 temp=x+y;
 return temp;
 }

int main() {
 int a=3;
 int b=2;
 int c;
 c=sum(a,b);
 return c;
 }

Because of the 'return c;' statement, we can display the returned value of the 'simplef.exe' program using the
echo %ERRORLEVEL%
command (or running the 'err' batch file) in the 'cmd' window.

Set the default path running the 'setpath' command (remember, only once). Compile, link and run the C program as follows:

Compile and run simplef.c using GCC

Now generate the corresponding 'simplef.s' assembly program with the
gcc simplef.c -S -o simplef.s
command. After deleting the unnecessary lines, we get the following assembly program:

	.text
	.globl	sum
sum:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$16, %rsp
	movl	%ecx, 16(%rbp)
	movl	%edx, 24(%rbp)
	movl	16(%rbp), %edx
	movl	24(%rbp), %eax
	addl	%edx, %eax
	movl	%eax, -4(%rbp)
	movl	-4(%rbp), %eax
	addq	$16, %rsp
	popq	%rbp
	ret

	.globl	main
main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$48, %rsp
	movl	$3, -4(%rbp)
	movl	$2, -8(%rbp)
	movl	-8(%rbp), %edx
	movl	-4(%rbp), %eax
	movl	%eax, %ecx
	call	sum
	movl	%eax, -12(%rbp)
	movl	-12(%rbp), %eax
	addq	$48, %rsp
	popq	%rbp
	ret

There are two important assembly instructions which have to be explained.

The call sum instruction first pushes the current value of the %rip instruction pointer onto the top of the stack, and then the value of the %rip instruction pointer will be set to the starting address of the 'sum' function. The performed operations can be described by the following instructions:

Although the %rip instruction pointer cannot be directly used in the assembly code, the instructions above "symbolically" corresponds to something like a 'pushq %rip' and 'leaq sum, %rip' (i.e. loading the address 'sum' to the %rip instruction pointer) instructions. Note that the latter instruction has the same result as the 'jmp sum' instruction.

The ret instruction is always the last instruction of any function. It "pops" the stored address of the next instruction to be executed from the top of the stack (in our case the address of the 'movl %eax, -12(%rbp)' instruction) and restores the value the instruction pointer with that address. The performed operations can be described by the following instructions:

Because we modified the value of the %rip instruction pointer, the next fetch-execute cycle will continue the execution of the program immediately after the 'call sum' instruction.

The diagram below illustrates the mechanism of the 'call' and the 'ret' (i.e. return) instructions:
Illustration of the call and ret instructions

Note that the called function is named the callee, and the program or function that calls the callee is named the caller.


Let us remember, that the simplef.s assembly program has two functions ('main' and 'sum'), and both functions contain their own stack frame: the 'sum' function creates a 16-byte long stack frame, and the 'main' function creates a 48-byte long stack frame. The doubleword length (32-bit long) contents of the stack of the 'main' function look like this:

We denoted the beginning (or bottom) of the stack frame of the 'main' function by GROUND_ZERO. Note that it is the current value of the %rsp stack pointer register just after the 'push %rbp' instruction has been executed.

When the 'sum' function is called from the 'main' function,
– the address of the next instruction immediately following the 'call sum' instruction in the 'main' function (which is the content of the %rip instruction pointer register) is pushed into the top of the stack,
– the 'sum' function saves the value of the %rbp basic pointer register, and then
– it creates its own 16-byte length stack frame.
After all that operations, the doubleword length (32-bit long) contents of the stack of the 'sum' function look like this:

We created a simulation program where you can follow the execution of instructions one-by-one, and have a look at the content of the registers and the corresponding part of the memory.



Implementation of other examples in Windows
II.1.3. Create and compile C files (continued)

Adding and printing 3+2 (example.c)

It is not very easy to check the programs if there is no direct visual output in them. So far, we set the return value of the programs to check the output. From now on, we shall use the well-known 'printf' function to display the desired output directly.

Open a new 'cmd' window again in the c:\temp\ directory. Using the notepad example.c command, create a new file named 'example.c' with the following content:

#include <stdio.h>

int main() {
 int a=3;
 int b=2;
 int c=a+b;
 printf("%d + %d = %d\n",a,b,c);
 return 0;
 }

Set the default path running the 'setpath' command. Compile, link and run the C program as follows:

Compile and run example.c using GCC

Like before, we can generate easily the assembly code of the 'example.c' program by entering the gcc example.c -S -o example.s command in the 'cmd' window.
The generated assembly program is as follows:

	.file	"example.c"
	.text
	.def	printf;
		.scl	3;
		.type	32;
		.endef
	.seh_proc	printf

printf:
	pushq	%rbp
	.seh_pushreg	%rbp
	pushq	%rbx
	.seh_pushreg	%rbx
	subq	$56, %rsp
	.seh_stackalloc	56
	leaq	48(%rsp), %rbp
	.seh_setframe	%rbp, 48
	.seh_endprologue
	movq	%rcx, 32(%rbp)	# 4th argument stored
	movq	%rdx, 40(%rbp)	# 3rd argument stored
	movq	%r8, 48(%rbp)	# 5th argument stored
	movq	%r9, 56(%rbp)	# 6th argument stored
	leaq	40(%rbp), %rax
	movq	%rax, -16(%rbp)
	movq	-16(%rbp), %rbx
	movl	$1, %ecx
	movq	__imp___acrt_iob_func(%rip), %rax
	call	*%rax
	movq	%rax, %rcx
	movq	32(%rbp), %rax
	movq	%rbx, %r8
	movq	%rax, %rdx
	call	__mingw_vfprintf
	movl	%eax, -4(%rbp)
	movl	-4(%rbp), %eax
	addq	$56, %rsp
	popq	%rbx
	popq	%rbp
	ret
	.seh_endproc
	.def	__main;
		.scl	2;
		.type	32;
		.endef
	.section .rdata,"dr"
.LC0:
	.ascii "%d + %d = %d\12\0"
	.text
	.globl	main
	.def	main;
		.scl	2;
		.type	32;
		.endef
	.seh_proc	main
main:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$48, %rsp
	.seh_stackalloc	48
	.seh_endprologue
	call	__main
	movl	$3, -4(%rbp)
	movl	$2, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	movl	%eax, -12(%rbp)
	movl	-12(%rbp), %ecx
	movl	-8(%rbp), %edx
	movl	-4(%rbp), %eax
	movl	%ecx, %r9d
	movl	%edx, %r8d
	movl	%eax, %edx
	leaq	.LC0(%rip), %rax
	movq	%rax, %rcx
	call	printf
	movl	$0, %eax
	addq	$48, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (GNU) 13.2.0"
	.def	__mingw_vfprintf;
		.scl	2;
		.type	32;
		.endef

Note that the label .LC0: identifies the format string necessary for the 'printf' function. In the format string the %d format specifiers tells the 'printf' function that it should print doubleword length (i.e. integer type) variables.

Based on the compiled program, we can easily create the 'examplex.s' assembly program which contains only quadword length operands.

Although it is very instructive to see the assembly implementation of the 'printf' function, using 'gcc' we can directly call the 'printf' function from an assembly program.
Open a new 'cmd' window in the c:\temp\ directory, run the notepad examplex.s command, and create a new file named 'examplex.s' with the following content:
.data
.msg:
	.ascii "%d + %d = %d\12\0"

.text
.globl	main
main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$48, %rsp
	movq	$3, -8(%rbp)	# local variable a
	movq	$2, -16(%rbp)	# local variable b
	movq	-8(%rbp), %rdx
	movq	-16(%rbp), %rax
	addq	%rdx, %rax
	movq	%rax, -24(%rbp)	# local variable c
	movq	-24(%rbp), %rcx
	movq	-16(%rbp), %rdx
	movq	-8(%rbp), %rax

	movq	%rcx, %r9	# 6th argument (var c)
	movq	%rdx, %r8	# 5th argument (var b)
	movq	%rax, %rdx	# 3rd argument (var a)
	leaq	.msg(%rip), %rax
	movq	%rax, %rcx	# 4th argument (pattern .msg)
	call	printf

	movq	$0, %rax
	addq	$48, %rsp
	popq	%rbp
	ret

Compile, link and run the assembly program as follows:

Compile and run examplex.s using GCC

There is one assembly instruction in the program which have to be explained.

First, the .msg label points to the format string of the 'printf' function (containing the %d format specifier which is used for printing integer values). In a so-called RIP-relative addressing assembly technique, this address can be accessed relatively to the %rip instruction pointer which always points to the address of the next instruction, i.e. the instruction that immediately follows the 'leaq' instruction (that has reference to the address). Of course, the compiler can easily count the relative address of the .msg label (called offset).

Second, the
leaq .msg(%rip), %rax
instruction moves the address .msg(%rip) into the %rax accumulator register, and then its value is moved to the %rcx register to be transferred as the 4th argument to the 'printf' function.

Third, the address .msg(%rip) of the format string is transferred to the 'printf' function through the %rcx register (carrying the 4th argument or parameter of the 'printf' function).

Note again, that the 'printf' function can be called from an assembly program directly.

Before we continue, let us briefly review the available registers of the x86/x64 architecture.



Registers of the Intel x86/x64 architecture

In x86/64 assembly programs, we use a lot of registers to store the necessary data of the program. Therefore it is high time to have an overview which registers are available for the assembly programs in the Intel x86/x64 architecture. First note, that using the AT&T assembly syntax,
– the 32 bit wide register names are prefixed with the %e characters, and
– the 64 bit wide register names are prefixed with the %r characters.

Note that when we declare an int type variable in C, its length will be 32 bit (i.e. it is doubleword wide).

In the Intel x86/x64 architecture the detailed list of some important registers is as follows (see e.g. X86-64 Architecture Guide, 2025-03-11; Assembly 1: Basics, 2025-03-30; x64 Architecture Overview and Registers, 2026-02-19):

General-purpose registers: Accumulator
Register Purpose Size Saved across calls
%rax the most important temporary register for arithmetic or logic calculations, moving instructions etc. (called accumulator)
return value of a function
64 bit No
%eax the lower half of the 8 byte wide %rax register 32 bit
%ax the lower half of the 4 byte wide %eax register 16 bit
%ah the higher half of the 2 byte wide %ax register 8 bit
%al the lower half of the 2 byte wide %ax register 8 bit
Structure of the accumulator
(%rax, 64 bit)
(%eax, 32 bit)
(%ax, 16 bit)
(%ah, 8 bit) (%al, 8 bit)
(MSB) (LSB)
Note that in memory the least significant byte (LSB) will be stored first (i.e. it will have the smallest address). It is called little-endian order. In case of file storage or network transfer, usually the most significant byte (MSB) will be stored or transferred first (and the least significant byte (LSB) will be the last). This is the opposite of the little-endian order and therefore called big-endian order. For example, if the %rax register contains a 8-byte long hex value 01 23 45 67 89 AB CD EF and its value is moved to the memory address 1000, then the following operations are performed in byte level:
M(1000) ← EF
M(1001) ← CD
M(1002) ← AB
M(1003) ← 89
M(1004) ← 67
M(1005) ← 45
M(1006) ← 23
M(1007) ← 01
Note that a selected part of the memory is usually visualized as moving down (↓) from the cells with lower memory addresses to the cells with higher memory addresses.
General-purpose registers: other registers
Register Purpose Size Saved across calls
%rbx callee-saved
Formerly we saw that the 'printf' function saves (push %rbx) the value of the %rbx register before any other instruction is performed, and at the end of the 'printf' function it reloads (pop %rbx) the %rbx register's saved content.
64 bit Yes
%ebx the lower half of the 8 byte wide %rbx register 32 bit
%bx the lower half of the 4 byte wide %ebx register 16 bit
%bh the higher half of the 2 byte wide %bx register 8 bit
%bl the lower half of the 2 byte wide %bx register 8 bit
%rcx used to pass 4th argument to functions 64 bit No
%ecx the lower half of the 8 byte wide %rcx register 32 bit
%cx the lower half of the 4 byte wide %ecx register 16 bit
%ch the higher half of the 2 byte wide %cx register 8 bit
%cl the lower half of the 2 byte wide %cx register 8 bit
%rdx used to pass 3rd argument to functions 64 bit No
%edx the lower half of the 8 byte wide %rdx register 32 bit
%dx the lower half of the 4 byte wide %edx register 16 bit
%dh the higher half of the 2 byte wide %dx register 8 bit
%dl the lower half of the 2 byte wide %dx register 8 bit
%rsi used to pass 2nd argument to functions 64 bit No
%esi the lower half of the 8 byte wide %rsi register 32 bit
%si the lower half of the 4 byte wide %esi register 16 bit
%sil the lower half of the 2 byte wide %si register 8 bit
%rdi used to pass 1st argument to functions 64 bit No
%edi the lower half of the 8 byte wide %rdi register 32 bit
%di the lower half of the 4 byte wide %edi register 16 bit
%dil the lower half of the 2 byte wide %di register 8 bit
%r8
used to pass 5th argument to functions
64 bit No
%r8d the lower half of the 8 byte wide %r8 register 32 bit
%r8w the lower half of the 4 byte wide %r8d register 16 bit
%r8b the lower half of the 2 byte wide %r8w register 8 bit
%r9
used to pass 6th argument to functions
64 bit No
%r9d the lower half of the 8 byte wide %r9 register 32 bit
%r9w the lower half of the 4 byte wide %r9d register 16 bit
%r9b the lower half of the 2 byte wide %r9w register 8 bit
%r10 temporary 64 bit No
%r11 temporary 64 bit No
%r12 callee-saved 64 bit Yes
%r13 callee-saved 64 bit Yes
%r14 callee-saved 64 bit Yes
%r15 callee-saved 64 bit Yes
Special-purpose registers
Register Purpose Size Saved across calls
%rsp stack pointer 64 bit Yes
%esp the lower half of the 8 byte wide %rsp register 32 bit
%sp the lower half of the 4 byte wide %esp register 16 bit
%spl the lower half of the 2 byte wide %sp register 8 bit
%rbp base pointer; callee-saved 64 bit Yes
%ebp the lower half of the 8 byte wide %rbp register 32 bit
%bp the lower half of the 4 byte wide %ebp register 16 bit
%bpl the lower half of the 2 byte wide %bp register 8 bit
%rip instruction pointer or program counter 64 bit (call↔ret)
%eip the lower half of the 8 byte wide %rip register 32 bit
%ip the lower half of the 4 byte wide %eip register 16 bit
%rflags status or control flags 64 bit No
%eflags the lower half of the 8 byte wide %rflags register 32 bit
%flags the lower half of the 4 byte wide %eflags register 16 bit

The status (or flags) register contains mostly one-bit storage units ("flags") that reflect the current state of an x86/x64 CPU. For example, some flags show some important characteristics of the result of arithmetic or logical operations (including comparisons etc.). Some usual flags are illustrated below within a 64-bit %rflags register:

63 ... 11 ... 7 6 5 4 3 2 1 0
OF SF ZF AF PF CF

The flag names are abbreviated as follows:

Formerly (e.g. in the mainframe age) the program counter and the status register were collectively called PSW (program status word) register. Nevertheless, this term can also be used for modern computers as well, including, in the first place, the content of the IP and the flags registers. "The PSW contains status information about the currently running process, including memory usage information, condition codes, and other status information such as an interrupt enable/disable bit and a kernel/user-mode bit." (Stallings 2018: 41)



Implementation of other examples in Windows
II.2. Create and compile assembly files

Table of contents:

  • Printing "Hello World!" (asmh.s)
  • Setting the ERRORLEVEL (abc.s)
  • Adding 3+2 (abcs.s)
  • Basic loop examples
    • Listing the first 10 natural numbers
    • Listing the first 10 even numbers
    • Listing the first 10 powers of 2
    • Listing the first 10 factorials

Printing "Hello World!" (asmh.s)

Now let us create a simple program in Intel x86/x64 assembly language which displays the well-known 'Hello world!' message. Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad asmh.s command, and create a new file named 'asmh.s' with the following content:

.globl	main

// definitions of constants and variables
.data

hello:
	.ascii "Hello world!\12\0"

// program instructions (code)
.text

main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$32, %rsp
	leaq	hello(%rip), %rax

/* setting the parameter for the function 'printf' */
	movq	%rax, %rcx	# address of 'hello'
	call	printf
/* displayed 'Hello world!' */

	movl	$0, %eax	# set ERRORLEVEL value
	addq	$32, %rsp
	popq	%rbp
	ret

Compile, link and run the assembly program as follows:

Compile and run asmh.s using GCC

Note that the size of the stack frame is 32 bytes, even though there are no local variables in the program. The four quadwords allocated at the top of the stack frame can be used for the (possible) parameters of the 'printf' function.

Setting the ERRORLEVEL (abc.s)

After we have successfully created and compiled the 'asmh.s' program, let us create another simple program in Intel x86/x64 assembly language which does nothing except returns the value 10 as an ERRORLEVEL value.

Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad abc.s command, and create a new file named 'abc.s' with the following content:

.globl main

main:
        enter $0, $0
        movq $10, %rax
        leave
        ret

Note that the first two instructions of the 'main' function creates a stack frame which, among others, can contain the values of the local variables (if there are any such variables at all). The basic pointer register (%rbp) is used as a reference to point to the address of those local variables (i.e. the local variables can be addressed relatively to the value of the basic pointer).

The 'enter $0, $0' assembly instruction corresponds to the
   pushq %rbp
   movq %rsp, %rbp
instructions. It creates the stack frame of the function.

Note that e.g. the 'enter $24, $0 instruction would allocate in the stack a 24 byte long memory space by subtracting 24 from the actual value of the stack pointer. Because 6*4=24 holds, this would be enough for six doubleword (i.e. 4 byte=32 bit) length local variables (or for three quadword length local variables, respectively).

The 'leave' assembly instruction corresponds to the
   movq %rbp, %rsp
   popq %rbp
instructions. It frees (or destroys) the stack frame of the function.

Using the push / pop instructions instead of the enter / leave instructions, we can create another version of the program 'abc.s' as follows:

.globl main

main:
        pushq %rbp
        movq %rsp, %rbp
        movq $10, %rax
	movq %rbp, %rsp
	popq %rbp
        ret

Compile, link and run the assembly program:

Compile and run abc.s using GCC

Here, like in the case of the 'simple.exe' program or the 'simplex.exe' program, we can display the returned value of the 'abc.exe' program using the echo %ERRORLEVEL% command in the 'cmd' window (or we can enter the 'err' command if the 'err.bat' file exists).

Adding 3+2 (abcs.s)

Now let us create an equivalent of the 'simple.c' program in Intel x86/x64 assembly language which adds two numbers (3 and 2) together as long integer types, stores the sum in another longint variable, and returns the sum as an ERRORLEVEL value. Before that, the program will warn us to check the actual value of the ERRORLEVEL environment variable.

Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad abcs.s command, and create a new file named 'abcs.s' with the following content:

.globl	main

.data 
hello:
	.ascii "\12See the ERRORLEVEL value!\12\0"

.text
main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$56, %rsp
	/* stack frame created */

	movq	$3, -8(%rbp)
	movq	$2, -16(%rbp)
	movq	-8(%rbp), %rax
	addq	-16(%rbp), %rax 
	movq	%rax, -24(%rbp)

	leaq	hello(%rip), %rcx
	call	printf

	movq	-24(%rbp), %rax	# set ERRORLEVEL value

	/* stack frame to be destroyed */
	addq	$56, %rsp
	popq	%rbp
	ret

Compile, link and run the assembly program as follows:

Compile and run abcs.c using GCC

Here, like in the case of the 'simple.exe' and 'abc.exe' programs, in the 'cmd' window we can display the returned value of the 'abcs.exe' program using the echo %ERRORLEVEL% or simply the err command. But in this case it returns the sum of the addition 3+2 (i.e. 5).

The size and content of the stack frame needs some explanation. The size of the stack frame is 56 bytes which corresponds to 7 quadwords (i.e. 56=7*8). The structure and content of the stack frame is as follows:

address content
0(%rsp) -56(%rbp) parameters for the function 'printf'
8(%rsp) -48(%rbp)
16(%rsp) -40(%rbp)
24(%rsp) -32(%rbp)
-24(%rbp) variable c
-16(%rbp) variable b
-8(%rbp) variable a
56(%rsp) 0(%rbp) previous value of %rbp (pushed by the first instruction of main)
8(%rbp) return address for the caller of 'main' (for 'ret' in main)
The basic pointer (%rbp) register has a special purpose: it points to the bottom of the stack frame of the current function, so local variables can be accessed relative to its value.

As for the last row of the table which belongs to the address 8(%rbp) just below the bottom of the stack frame, when the program environment (i.e. the cmd.exe in our case) runs the abcs.exe program, it calls the 'main' global function of the abcs.exe program, and the current value of the instruction pointer is automatically pushed onto the top of the stack. (Thus when the called 'main' function exits and returns, the CPU can continue the execution of the caller program by popping the address of the next instruction to be performed from the stack and loading it into the instruction pointer).

In general, when a specific function of the program is called by another function (from the same or from another program), the return address of the next instruction to be executed after the 'call' instruction is automatically pushed onto the top of the stack.

Note that in the fetch-execute cycle the address of the next instruction is always stored in the %rip instruction pointer or program counter register. Thus the 'call' function, when executed, pushes the current value of the instruction pointer onto the top of the stack. After that the called function pushes the value of the basic pointer into the stack and creates its stack frame.

Listing the first 10 natural numbers

First, let us see a C program that prints the first 10 natural numbers (starting with 1, then 2, 3, 4, ..., 10). Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad natural.c command, and create a new file named 'natural.c' with the following content:

#include <stdio.h>

int main() {
 int x=1;
 int i, n=10;
 for(i=1;i<=n;i++) {
  printf("%d\n",x);
  x++;
  }
 return i;
 }

Compile, link and run the compiled C program as follows:

Compile and run natural.c using GCC

Now it can be very instructive to see the compiled assembly version of the C program. Type and run in the 'cmd' window the gcc natural.c -S -o nat.s command. After making some changes (omitting some parts, commenting some of the instructions etc.), the resulting file will look like something like this:

.data
.pattern:
	.ascii "%d\12\0"

.text
printf:
	pushq	%rbp
	pushq	%rbx		# callee saved
	subq	$56, %rsp
	leaq	48(%rsp), %rbp
/* 56=48+8; pushing %rbx allocates +8 bytes at the stack */

	movq	%rcx, 32(%rbp)	# 4th argument stored
	movq	%rdx, 40(%rbp)	# 3rd argument stored
	movq	%r8, 48(%rbp)	# 5th argument stored
	movq	%r9, 56(%rbp)	# 6th argument stored

	leaq	40(%rbp), %rax
	movq	%rax, -16(%rbp)	# local variable
	movq	-16(%rbp), %rbx
	movl	$1, %ecx
	movq	__imp___acrt_iob_func(%rip), %rax
	call	*%rax

	movq	%rax, %rcx
	movq	32(%rbp), %rax
	movq	%rbx, %r8
	movq	%rax, %rdx
	call	__mingw_vfprintf
	movl	%eax, -4(%rbp)
	movl	-4(%rbp), %eax

	addq	$56, %rsp
	popq	%rbx
	popq	%rbp
	ret

.text
.globl	main
main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$48, %rsp	# stack frame (48 bytes)
	call	__main

	movl	$1, -4(%rbp)	# variable x
	movl	$10, -12(%rbp)	# variable n
	movl	$1, -8(%rbp)	# variable i
	jmp	.L4

/* begin of loop */
.L5:
	movl	-4(%rbp), %eax	# variable x to print
	movl	%eax, %edx	# 3rd parameter (printf)
	leaq	.pattern(%rip), %rax	# copy address
	movq	%rax, %rcx	# 4th parameter (printf)
	call	printf

	addl	$1, -4(%rbp)	# x++
	addl	$1, -8(%rbp)	# i++
.L4:
	movl	-8(%rbp), %eax	# i -> %eax
	cmpl	-12(%rbp), %eax	# i≤n ?
	jle	.L5		# jump if i≤n
/* end of loop */

	movl	-8(%rbp), %eax
	addq	$48, %rsp
	popq	%rbp
	ret

Before the 'printf' function is called and the local variables are "declared" (i.e. before the 'jmp .L4' instruction), the content of the stack frame created by the 'main' function is as follows:

address content
0(%rsp) -48(%rbp) allocated space for the four parameters (or arguments) for the function 'printf'
8(%rsp) -40(%rbp)
16(%rsp) -32(%rbp)
24(%rsp) -24(%rbp)
(not used)
-12(%rbp) variable n (initially n=10)
-8(%rbp) variable i (initially i=1)
-4(%rbp) variable x (initially x=1)
48(%rsp) 0(%rbp) previous value of %rbp (pushed by the first instruction of main)
8(%rbp) return address for the caller of 'main' (for 'ret' in main)

After the 'printf' function is called for the first time by the 'main' function, the content of the stack frame created by the 'printf' function is as follows:

address content
0(%rsp) -48(%rbp)
... ... ...
48(%rsp) 0(%rbp)
56(%rsp) 8(%rbp) previous value of %rbx (pushed by the second instruction of printf)
16(%rbp) previous value of %rbp (pushed by the first instruction of printf)
24(%rbp) return address for the caller of 'printf', i.e. the address of the next instruction of 'main' after the 'call printf' instruction
(the following part of the stack is the same space for the arguments (or parameters) of the 'printf' function that has been allocated by the 'main' function, see the top of its stack frame in the table above)
32(%rbp) 4th argument of the function 'printf'
40(%rbp) 3rd argument of the function 'printf'
48(%rbp) 5th argument of the function 'printf'
56(%rbp) 6th argument of the function 'printf'

Let us now create an equivalent of the above program in assembly language which prints the first 10 natural numbers and uses quadword length local variables. Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad natural.s command, and create a new file named 'natural.s' with the following content:

.data 
msg:
	.ascii "The first %d natural numbers:\12\0"

pattern:
	.ascii "%d\12\0"

.text
.globl	main
main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$64, %rsp

	movl	$1, -4(%rbp)	# x
	movl	$10, -12(%rbp)	# n
	movl	$1, -8(%rbp)	# i

	leaq	msg(%rip), %rcx
	movl	-12(%rbp), %edx
	call	printf

.L0:	
	movl	-8(%rbp), %eax
	cmpl	-12(%rbp), %eax	# i>n ?
	jg	.L1

	leaq	pattern(%rip), %rcx
	movl	-4(%rbp), %edx
	call	printf

	incl	-4(%rbp)
	incl	-8(%rbp)
	jmp	.L0

.L1:
	movl	$0, %eax	# return value
	addq	$64, %rsp
	popq	%rbp
	ret

Compile, link and run the assembly program as follows:

Compile and run natural.s using GCC

Listing the first 10 even numbers

Using the assembly instructions we have learned so far, we can easily list the first even numbers. Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad even.s command, and create a new file named 'even.s' with the following content:

.data
.msg:
	.ascii "i=%d, x=%d, n=%d\12\0"

.text
.globl	main
main:
	pushq	%rbp
	movq	%rsp, %rbp
	// pushq	%rbx
	subq	$64, %rsp
	movq	%rbx, -8(%rbp)

	movq	$1, -16(%rbp)	# local variable i (loop variable)
	movq	$0, -24(%rbp)	# local variable x 
	movq	$10, -32(%rbp)	# local variable n

loop:
	movq	-32(%rbp), %r9	# 4th argument (var n)
	movq	-24(%rbp), %r8	# 3th argument (var x)
	movq	-16(%rbp), %rdx	# 2rd argument (var i)
	leaq	.msg(%rip), %rcx
	call	printf

        movq	-24(%rbp), %rbx
	addq	$2, %rbx
        movq	%rbx, -24(%rbp)

        movq	-16(%rbp), %rax
	addq	$1, %rax
        movq	%rax, -16(%rbp)

	cmpq	-32(%rbp), %rax	# compare 'n' with 'i'
	jle	loop	# jump if i<=n

	movq	-8(%rbp), %rbx
	movl	$0, %eax
	addq	$64, %rsp
	// popq	%rbx
	popq	%rbp
	ret

Note that there is one important solution in the above assembly program. We used the %rbx general-purpose register which should be callee-saved. But saving its value by the 'push' and 'pop' instructions caused some problems in our environment (using Windows 11 and gcc-13.2.0, 2026-03-05). So we simply saved the content of the %rbx register as the first quadword in the stack frame of the 'main' function, using the movq %rbx, -8(%rbp) instruction.

Listing the first 10 powers of 2

First, let us see a C program that prints the first 10 powers of 2 (starting with 1, then 2, 4, 8 etc.). Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad powers.c command, and create a new file named 'powers.c' with the following content:

#include <stdio.h>

int nextpow(int x) {
 int p=x+x;
 return p;
 }

int main() {
 int x=1;
 int i=1, n=10;
 do {
  printf("%d\n",x);
  x=nextpow(x);
  i++;
  } while(i<=n);
 return i;
 }

Compile, link and run the compiled C program as follows:

Compile and run powers.c using GCC

Let us now create an equivalent of the 'powers.c' program in Intel x86/x64 assembly language which prints the first 10 powers of 2. Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad powers.s command, and create a new file named 'powers.s' with the following content:

.globl	main

.data
pattern:
	.ascii "%d\12\0"

.text
nextpow:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$16, %rsp	# stack frame size
	movl	%ecx, 16(%rbp)	# parameter x
	movl	16(%rbp), %eax
	addl	%eax, %eax
	movl	%eax, -4(%rbp)	# local variable p
	movl	-4(%rbp), %eax
	addq	$16, %rsp
	popq	%rbp
	ret

main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$48, %rsp
	movl	$1, -4(%rbp)	# variable x
	movl	$1, -8(%rbp)	# variable i
	movl	$10, -12(%rbp)	# variable n

.loop:
	movl	-4(%rbp), %edx
	leaq	pattern(%rip), %rcx
	call	printf
	movl	-4(%rbp), %ecx
	call	nextpow
	movl	%eax, -4(%rbp)
	addl	$1, -8(%rbp)	# i++
	movl	-8(%rbp), %eax
	cmpl	-12(%rbp), %eax	# i<=n ?
	jle	.loop

	movl	-8(%rbp), %eax
	addq	$48, %rsp
	popq	%rbp
	ret

Compile, link and run the assembly program as follows:

Compile and run powers.s using GCC

Listing the first 10 factorials

First, let us see a C program that prints the first 10 factorials (starting with 1, then 2, 6, 24 etc.). Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad fact.c command, and create a new file named 'fact.c' with the following content:

#include <stdio.h>

int f(int n) {
 int temp;
 temp=1;
 for(int i=2;i<=n;i++) {
  temp=temp*i;
  }
 return temp;
 }

int main() {
 int n=10;
 printf("List of the first %d factorials:\n",n);
 int i=1;
 while(i<=n) {
  printf("%d\n",f(i));
  i=i+1;
  };
 return i;
 }

Compile, link and run the compiled C program as follows:

Compile and run powers.c using GCC

Let us now create an equivalent of the 'fact.c' program in Intel x86/x64 assembly language which prints the first 10 factorials. Open a new 'cmd' window in the c:\temp\gcc directory, run the notepad fact.s command, and create a new file named 'fact.s' with the following content:

.globl	main
.globl	f

.data
.LC0:
	.ascii "List of the first %d factorials:\12\0"
.LC1:
	.ascii "%d\12\0"

.text

f:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$16, %rsp

	movl	%ecx, 16(%rbp)
	movl	$1, -4(%rbp)
	movl	$2, -8(%rbp)
	jmp	.L4
.L5:
	movl	-4(%rbp), %eax
	imull	-8(%rbp), %eax
	movl	%eax, -4(%rbp)
	addl	$1, -8(%rbp)
.L4:
	movl	-8(%rbp), %eax
	cmpl	16(%rbp), %eax
	jle	.L5

	movl	-4(%rbp), %eax
	addq	$16, %rsp
	popq	%rbp
	ret

main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$48, %rsp

	movl	$10, -8(%rbp)
	movl	-8(%rbp), %eax
	movl	%eax, %edx
	leaq	.LC0, %rax
	movq	%rax, %rcx
	call	printf

	movl	$1, -4(%rbp)
	jmp	.L8
.L9:
	movl	-4(%rbp), %eax
	movl	%eax, %ecx
	call	f
	movl	%eax, %edx
	leaq	.LC1, %rax
	movq	%rax, %rcx
	call	printf
	addl	$1, -4(%rbp)
.L8:
	movl	-4(%rbp), %eax
	cmpl	-8(%rbp), %eax
	jle	.L9

	movl	-4(%rbp), %eax
	addq	$48, %rsp
	popq	%rbp
	ret

Note that the imul assembly instruction executes a signed multiplication or product of the first operand (which can be either a register or a word-length or doubleword-length memory content) and the second operand (a register), and stores the resulting product in the the register specified as the second operand.

Compile, link and run the assembly program as follows:

Compile and run fact.s using GCC



Review questions and exercises

Questions:

Create programs in x86/x64 assembly language which performs the following tasks:

Each program should start with printing its function and end with the name of the programmer (as well as the current date).



Boda István, 2025-2026.