Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Assembly (x86-64, AArch64, RISC-V)

Assembly flashcards: x86-64, AArch64, RISC-V

Recall cards for every assembly lesson: data, registers, memory, flags, branches, calls, system calls, SIMD, compiler output, debugging, 8051 and Arm.

497 cards. Read the question, answer it in your head, then open the card to check.

  1. How do an ISA, assembly language and machine code relate to each other?

    The ISA is the contract a processor offers: its registers, its instructions, how they are encoded and the rules for memory and privilege. Machine code is the byte stream written to that contract, and assembly language spells those same bytes as readable lines, about one instruction per line.

    From the lesson What assembly language is and why it matters

  2. What does an assembler do, and what does a disassembler do?

    An assembler turns assembly text into the matching instruction bytes in an object file. A disassembler goes the other way: it reads the bytes of a binary and prints the instructions they encode, so code without source can still be read.

    From the lesson What assembly language is and why it matters

  3. Inside one instruction, what job does the opcode do?

    The opcode is the group of bits that picks the operation. The other bits of the instruction name the registers, constants and modes it works on, and the ISA fixes where each field sits.

    From the lesson What assembly language is and why it matters

  4. Which three ISAs does the track cover, and where is each one found?

    x86-64 runs most PCs and servers, AArch64 runs phones and Apple silicon Macs, and RISC-V is the open ISA of new and emerging hardware. The same tiny program gets different registers and different bytes on each.

    From the lesson What assembly language is and why it matters

  5. What are four reasons people still read assembly?

    To check what a compiler produced, to debug a crash when no source is at hand, to tune hot code, and to confirm that security hardening really made it into the binary. Operating systems, firmware and trusting third-party code need it too.

    From the lesson What assembly language is and why it matters

  6. Why does a program assembled for one ISA not run on another?

    Each ISA has its own instructions, registers and encodings, so bytes written for one processor family mean something else, or nothing, to another. That is also why every ISA has its own assembly language.

    From the lesson What assembly language is and why it matters

  7. Which targets can NASM, GNU as and LLVM's assembler each produce code for?

    NASM handles x86 and x86-64 only. GNU as supports all three families, but each build targets one, so you install one cross build per ISA. LLVM can hold all three targets in one program, provided the copy you installed was built with them.

    From the lesson Set up assemblers, QEMU and debuggers

  8. How can you tell whether your clang can write RISC-V code?

    Run clang -print-targets and look for riscv64. Apple clang 21.0.0 does not list RISC-V, so on a Mac RISC-V objects come from Homebrew's LLVM (23.1.2 in the track's runs) or from a Linux machine.

    From the lesson Set up assemblers, QEMU and debuggers

  9. Your laptop has an AArch64 processor. How do x86-64 and RISC-V Linux programs run on it?

    Only AArch64 code runs natively. The other two run under qemu-user, QEMU's user-mode emulator, inside a Linux environment such as a virtual machine.

    From the lesson Set up assemblers, QEMU and debuggers

  10. How does Linux hand a foreign ELF program to qemu-user when you start it by name?

    binfmt_misc compares the first bytes of the file, which include the ELF machine field (62 for x86-64, 183 for AArch64, 243 for RISC-V), with its registered patterns. The entry that matches names the qemu-user emulator that runs the program.

    From the lesson Set up assemblers, QEMU and debuggers

  11. Why run a version report, one line per tool, before the first lab?

    It shows a missing or outdated assembler, emulator or debugger up front, measured against the track's minimum versions, instead of leaving you to discover it when a program fails to build or run.

    From the lesson Set up assemblers, QEMU and debuggers

  12. Which debugger fits a program on macOS, one on Linux, and one running under qemu-user?

    LLDB for macOS programs and GDB for Linux programs. A program under qemu-user can wait on the emulator's -g port, and GDB attaches to that port.

    From the lesson Set up assemblers, QEMU and debuggers

  13. When must you read $? to get the exit status of a command?

    On the very next line. $? holds the status of the most recent pipeline, so any command in between, even an echo, replaces the value you wanted.

    From the lesson First program: return an exit status

  14. On an Apple silicon Mac, how does an assembly _main that ends with ret produce exit status 42?

    It leaves 42 in w0 and returns. The start-up code in dyld and libSystem called _main, so it receives the 42 and hands it to exit, which ends the process with that status.

    From the lesson First program: return an exit status

  15. A Linux program built without any library must end itself. Which call number and instruction does each ISA use?

    The exit system call: 60 in eax with syscall on x86-64, 93 in x8 with svc 0 on AArch64, and 93 in a7 with ecall on RISC-V. The status goes in the first argument register.

    From the lesson First program: return an exit status

  16. A program exits with 300. What does the shell report, and why?

    44. The kernel stores just the lowest 8 bits, and 300 mod 256 is 44; by the same rule 256 reads back as 0 and -1 as 255.

    From the lesson First program: return an exit status

  17. Why keep exit statuses between 0 and 125?

    The shell reserves 126 for a file it cannot execute, 127 for a command it cannot find and 128 plus N for a process killed by signal N, and values above 255 wrap around. A status from 0 to 125 cannot be confused with any of those.

    From the lesson First program: return an exit status

  18. Why does a program that defines only _start fail to link on a Mac with an undefined _main error?

    The macOS linker looks for main, which Mach-O spells _main because it puts an underscore in front of every C symbol. ELF adds no underscore, and a Linux program without a library starts at _start, so a Linux-style file leaves _main undefined.

    From the lesson First program: return an exit status

  19. What are the four stages between an assembly source file and a running process?

    Assemble: the assembler writes an object file with holes in it. Link: the linker lays the sections out and fills the holes. Load: the loader maps the segments into memory and jumps to the entry point. Run: the processor executes the code.

    From the lesson From source to executable: objects and linking

  20. What three things does an object file hand to the linker?

    The raw bytes of each named section, a symbol table listing every label it defines or uses, and relocation entries naming which bytes to patch with which symbol's address.

    From the lesson From source to executable: objects and linking

  21. Why does an assembler leave relocations instead of final addresses?

    While it writes one object it cannot know where the linker will place each section, or where symbols from other objects will end up. A relocation records the bytes to patch, the symbol and the formula, and the linker applies it once the addresses are fixed.

    From the lesson From source to executable: objects and linking

  22. Which container formats wrap the same instruction bytes on Linux, macOS and Windows, and how do they differ?

    Linux and most Unix systems use ELF, with a section header table for the linker and a program header table for the loader. Apple uses Mach-O, whose single list of load commands serves both. Windows uses PE/COFF: the file opens with a small DOS stub that points to the PE signature, where the real header begins.

    From the lesson From source to executable: objects and linking

  23. Where does a program start, and how does linking a C runtime change that?

    At the entry-point address named in the executable's header. With a C runtime the entry is the runtime's start stub, which sets things up and then calls main; a bare _start program has no caller and must make the exit system call itself.

    From the lesson From source to executable: objects and linking

  24. Why can llvm-objdump and llvm-readelf show x86-64 and RISC-V objects faithfully on an Apple silicon Mac?

    They only read the bytes of the file and never execute them, so the host's own ISA does not matter. Running those programs is different: that needs a Linux system of the right ISA, or an emulator.

    From the lesson From source to executable: objects and linking

  25. How do Intel and AT&T syntax write the same x86-64 instruction differently?

    Intel syntax puts the destination first and writes registers and numbers bare. AT&T syntax puts the destination last, marks registers with % and immediates with $, and writes memory as disp(base, index, scale). Both assemble to identical bytes.

    From the lesson Assembler syntax: NASM, GNU as, Intel and AT&T

  26. Which tools use AT&T syntax by default, and how do you switch GNU as to Intel syntax?

    GNU as and objdump default to AT&T. The directive .intel_syntax noprefix switches GNU as to the destination-first order that NASM and Intel's manuals use.

    From the lesson Assembler syntax: NASM, GNU as, Intel and AT&T

  27. When must you state an operand size in x86-64 assembly, and how is it written in each syntax?

    When no register fixes the size of a memory operand, as when storing a constant. AT&T adds a suffix to the mnemonic (b, w, l, q), GNU Intel syntax writes dword ptr and NASM writes dword. Leave it out and NASM 3.02 stores one byte without a warning.

    From the lesson Assembler syntax: NASM, GNU as, Intel and AT&T

  28. What does a semicolon mean in NASM, and what does it mean in GNU as for x86?

    In NASM it starts a comment. In GNU as for x86 it separates two statements on one line, so the text after it is assembled as code.

    From the lesson Assembler syntax: NASM, GNU as, Intel and AT&T

  29. How do NASM and GNU as treat local labels differently?

    NASM keeps its local .name labels as symbols in the object file, while GNU as drops every label that begins with .L, so those never reach the symbol table.

    From the lesson Assembler syntax: NASM, GNU as, Intel and AT&T

  30. How many bytes does .word emit on x86-64, and how many on AArch64 or RISC-V?

    Two bytes on x86-64 but four on AArch64 and RISC-V, because each target's assembler defines its own word size.

    From the lesson Assembler syntax: NASM, GNU as, Intel and AT&T

  31. What does a RISC-V pseudo-instruction such as li or la turn into, and how do you see it?

    One to eight real instructions, depending on the value and the options; la also changes when the code is position-independent. llvm-objdump -M no-aliases prints the instructions that were really written.

    From the lesson Assembler syntax: NASM, GNU as, Intel and AT&T

  32. Why can an AArch64 mov with a constant fail to assemble?

    mov is always exactly one instruction, an alias of movz, movn, orr or add. A constant that none of them can encode is an error, and you build it instead with a movz followed by movk instructions.

    From the lesson Assembler syntax: NASM, GNU as, Intel and AT&T

  33. How many bits does one hexadecimal digit stand for, and how many digits fill a 64-bit register?

    One hex digit is exactly four bits, a nibble. Two digits make a byte and sixteen digits fill a 64-bit register, so turning binary into hex is a matter of cutting the bits into groups of four from the right.

    From the lesson Bits, bytes, words and hexadecimal

  34. How do you turn 300 into hexadecimal by hand, and how do you check the answer?

    Divide by 16 repeatedly and read the remainders from last to first: 300 is 0x12C. Check by adding each digit times its power of 16: 1 x 256 + 2 x 16 + 12 = 300.

    From the lesson Bits, bytes, words and hexadecimal

  35. Is a word 16 bits or 32 bits?

    It depends on the manual. Intel's x86-64 manual keeps the 8086 meaning, so a word is 16 bits, a doubleword 32 and a quadword 64. AArch64 and RISC-V call 32 bits a word and 64 bits a doubleword. Say the size in bits next to the name.

    From the lesson Bits, bytes, words and hexadecimal

  36. How does each ISA say how many bytes a load moves?

    x86-64 writes byte, word, dword or qword ptr on the memory operand. AArch64 uses ldrb and ldrh, or ldr into a w or an x register. RISC-V has separate loads: lb, lh, lw and ld.

    From the lesson Bits, bytes, words and hexadecimal

  37. What happens to the upper bits of a 64-bit register when you write only a narrow part of it?

    A 32-bit write (eax on x86-64, w0 on AArch64) clears the upper 32 bits. An 8-bit or 16-bit x86 write (al, ax) leaves them as they were. An RV64 W instruction fills them with copies of bit 31.

    From the lesson Bits, bytes, words and hexadecimal

  38. How many patterns does a byte hold, and how are they written in hexadecimal?

    256 patterns, from 0x00 to 0xFF. A byte is 8 bits and the smallest unit of memory with its own address on x86-64, AArch64 and RISC-V.

    From the lesson Bits, bytes, words and hexadecimal

  39. How do you negate a number in two's complement?

    Invert every bit, then add 1. The result is the pattern that adds to the original and leaves zero in the register.

    From the lesson Two's complement and sign extension

  40. What is the byte 0xc8 as a signed number, and why?

    -56. In two's complement the top bit of an n-bit value is worth minus 2 to the power n-1, so 0xc8 is -128 + 72 = -56. Read as unsigned, the same byte is 200.

    From the lesson Two's complement and sign extension

  41. Why does negating -128 in an 8-bit register give -128 again?

    Eight bits hold -128 to 127: the negatives outnumber the positives by one, so +128 has no pattern. Inverting 0x80 and adding 1 lands on 0x80 again.

    From the lesson Two's complement and sign extension

  42. One adder gives the sum for signed and unsigned numbers. How do you tell that each reading did not fit?

    For unsigned numbers, a carry out of the top bit. For signed numbers, two inputs of the same sign giving a result of the other sign. x86-64 records them in CF and OF, AArch64's flag-setting instructions in C and V, and RISC-V code checks them with branches.

    From the lesson Two's complement and sign extension

  43. The byte 0x80 is widened to 64 bits by zero extension and by sign extension. What does each give?

    Zero extension gives 0x0000000000000080, which is 128 and keeps the unsigned reading. Sign extension copies bit 7 into every new bit and gives 0xffffffffffffff80, which is -128 and keeps the signed reading.

    From the lesson Two's complement and sign extension

  44. Which loads zero-extend and which sign-extend a byte on each ISA?

    movzx and movsx on x86-64, ldrb and ldrsb on AArch64, lbu and lb on RISC-V. Pick by the type of the value, and remember that a 32-bit destination on AArch64 and x86-64 stops the extension at bit 31 and clears the rest.

    From the lesson Two's complement and sign extension

  45. Is plain char signed or unsigned?

    The ABI decides. It is signed on x86-64 and Apple silicon but unsigned on Linux for AArch64 and RISC-V, so code that treats bytes as numbers should say unsigned char or uint8_t.

    From the lesson Two's complement and sign extension

  46. On x86-64, AArch64 and RISC-V, in what order do the bytes of 0x1122334455667788 sit in memory?

    88 77 66 55 44 33 22 11: the lowest-value byte at the lowest address. All three are little-endian in practice, the reverse of how the number is written on paper.

    From the lesson Endianness, strings and text in memory

  47. What is big-endian order, and where is it still common?

    The highest-value byte sits at the lowest address. Internet protocols use it on the wire and call it network byte order.

    From the lesson Endianness, strings and text in memory

  48. Which instruction reverses the byte order of a register on each ISA?

    bswap on x86-64 (movbe does it as part of a load or store), rev on AArch64, with rev16 and rev32 for smaller groups, and rev8 on RISC-V, which needs the Zbb extension that RV64GC does not include.

    From the lesson Endianness, strings and text in memory

  49. How do C strings and calls like write(2) know where text ends?

    A C string ends at a null terminator, one byte of value 0 that strlen and printf walk forward to. write(2) and other length-based interfaces take the number of bytes as a separate argument instead.

    From the lesson Endianness, strings and text in memory

  50. What are the code point count and the UTF-8 byte count of the Devanagari word namaste, and why?

    6 code points but 18 bytes. Devanagari sits in U+0900 to U+097F, and UTF-8 needs three bytes for each code point in that range; ASCII takes one byte, and the rocket emoji four.

    From the lesson Endianness, strings and text in memory

  51. Why is a buffer sized by character count a bug for most non-English text?

    UTF-8 stores many characters in two to four bytes, so the encoded text is longer than the count of characters. Size buffers by the encoded length, and decode or skip continuation bytes when you count characters.

    From the lesson Endianness, strings and text in memory

  52. What are the three fields of a binary64 double, and how is its value read back?

    A sign bit, an 11-bit exponent biased by 1023 and a 52-bit fraction. A normal value is (-1)^sign x 1.fraction x 2^(exponent - 1023), with the leading 1 implied and never stored.

    From the lesson Floating-point numbers bit by bit

  53. How does binary32, the C float, differ from binary64?

    Same shape, smaller fields: an 8-bit exponent biased by 127 and a 23-bit fraction after the sign bit, so 24 bits of significand instead of 53.

    From the lesson Floating-point numbers bit by bit

  54. Why is 0.1 + 0.2 not equal to 0.3 in doubles?

    None of the three values is exact in binary, so each is rounded to the nearest double when stored. The sum comes out as 0x3FD3333333333334, which is one ulp higher than the stored 0.3. Compare floating-point values with a tolerance.

    From the lesson Floating-point numbers bit by bit

  55. Which exponent patterns give zeros, subnormals, infinities and NaNs?

    An all-zero exponent gives the zeros (fraction 0) and the subnormals (fraction non-zero). An all-one exponent gives the infinities (fraction 0) and the NaNs (fraction non-zero). A quiet NaN has the top fraction bit set; a signalling NaN has it clear.

    From the lesson Floating-point numbers bit by bit

  56. How can code detect a NaN without looking at its bits?

    Test x != x. A NaN compares unequal to everything, itself included, while every other value equals itself.

    From the lesson Floating-point numbers bit by bit

  57. How do you copy the raw 64 bits of a double into an integer register on each ISA?

    movq rax, xmm0 on x86-64, fmov x0, d0 on AArch64 and fmv.x.d a0, fa0 on RISC-V. They copy the bits unchanged; fcvtzs and its kin convert the value to an integer instead.

    From the lesson Floating-point numbers bit by bit

  58. How wide is long double?

    It is an ABI choice. On an Apple silicon Mac it is 8 bytes, the same as double. The Linux ABIs give it 16 bytes, holding binary128 on AArch64 and RISC-V and the old x87 80-bit extended format on x86-64.

    From the lesson Floating-point numbers bit by bit

  59. What loop does a processor repeat for every instruction?

    Fetch the bytes at the address in the program counter, decode them into an operation and its operands, execute it, then move the program counter on to the next instruction. The loop runs until the program ends or asks the kernel to stop it.

    From the lesson Fetch, decode, execute: the program counter

  60. What is the program counter called on each ISA, and how far does it move after a straight-line instruction?

    rip on x86-64 and pc on AArch64 and RISC-V. It advances by the length of the instruction just fetched: always 4 bytes for RISC-V base instructions, anything from 1 to 15 bytes on x86-64.

    From the lesson Fetch, decode, execute: the program counter

  61. What makes an instruction a branch?

    Its job is to write the program counter. Taken conditional branches, jumps, calls and returns all set it to a new address, so the next fetch reads from there instead of the following instruction; loops, if/else and calls are built from that alone.

    From the lesson Fetch, decode, execute: the program counter

  62. Why can ordinary instructions not name the program counter as an operand, and how is it read instead?

    If any arithmetic could write it, any calculation could turn into a jump by accident. Each ISA offers PC-relative forms instead: lea with rip on x86-64, adr and adrp on AArch64, auipc on RISC-V.

    From the lesson Fetch, decode, execute: the program counter

  63. Modern cores pipeline, predict and reorder instructions. Why is the one-at-a-time cycle still the right model for reading code?

    Because the architectural result is always the same as if instructions ran one after another in program order. Overlap and reordering change how long the work takes, never what the program computes.

    From the lesson Fetch, decode, execute: the program counter

  64. How many general-purpose registers do x86-64, AArch64 and RISC-V have?

    16 on x86-64 (rax to r15), 31 on AArch64 (x0 to x30) and 32 on RISC-V (x0 to x31), where x0 always reads as zero.

    From the lesson Registers on x86-64, AArch64 and RISC-V

  65. Are al, ax, eax and rax four registers?

    No: they are windows onto one 64-bit register, its low 8, 16, 32 and all 64 bits. In the same way w0 is the low half of x0 on AArch64.

    From the lesson Registers on x86-64, AArch64 and RISC-V

  66. Why does writing eax clear the top half of rax on x86-64 while writing al leaves it alone?

    They are two separate rules in Intel's manual. A 32-bit write zero-extends into the whole register, which also spares the core from waiting on the old top half; 8-bit and 16-bit writes change only their own bits. Code that tweaks a 64-bit value with a 32-bit add loses the top half.

    From the lesson Registers on x86-64, AArch64 and RISC-V

  67. What happens to x0 when an AArch64 instruction writes w0?

    The top 32 bits of x0 are cleared, every time. AArch64 has no 8-bit or 16-bit register names, so the 32-bit view is the only narrow write.

    From the lesson Registers on x86-64, AArch64 and RISC-V

  68. Which registers have fixed jobs: the stack pointer, the link register and the zero register?

    The stack pointer is rsp on x86-64 and sp on AArch64 and RISC-V. The return address goes in x30 on AArch64 and ra on RISC-V, while x86-64 keeps it on the stack. RISC-V x0 and AArch64 xzr or wzr always read as zero, and RISC-V has no flags register.

    From the lesson Registers on x86-64, AArch64 and RISC-V

  69. Which registers should ordinary code never clobber?

    x18 on AArch64, the platform register, and gp and tp on RISC-V, unless you own the whole program. Never destroy the stack pointer, and never lose a link register you have not saved.

    From the lesson Registers on x86-64, AArch64 and RISC-V

  70. What regions does a process see in its virtual address space?

    Code (.text), read-only constants (.rodata), initialised data (.data), zeroed data (.bss), the heap, shared libraries and other mmap mappings, and the stack. Each region carries its own read, write and execute permissions.

    From the lesson Memory and a process's address space

  71. How does the memory-management unit keep each process's memory private?

    It translates virtual addresses to physical memory one page at a time, through page tables that belong to each process. Two processes can use the same virtual address and reach different physical pages, and neither can touch the other's.

    From the lesson Memory and a process's address space

  72. Why does a null pointer dereference fault at once instead of reading garbage?

    The page at address 0 is left unmapped, so the memory-management unit has no translation for it and raises a fault on the first access.

    From the lesson Memory and a process's address space

  73. Is a page always 4096 bytes?

    No. An Apple silicon Mac reports 16384, and Linux on arm64 may use 4, 16 or 64 KiB pages. Ask at run time with sysconf(_SC_PAGESIZE) or the AT_PAGESZ entry instead of assuming 4 KiB.

    From the lesson Memory and a process's address space

  74. How do you round a size up to whole pages, and what does 70000 bytes become?

    Compute (size + pagesize - 1) and then clear the low bits with & ~(pagesize - 1). 70000 bytes becomes 73728 (18 pages) with 4 KiB pages and 81920 (5 pages) with 16 KiB pages.

    From the lesson Memory and a process's address space

  75. Why must a program or a test never rely on a fixed address?

    Address-space layout randomisation places the stack, heap and libraries at different addresses on each run, and a position-independent executable lets the code itself move too.

    From the lesson Memory and a process's address space

  76. What is the trade between fixed-length and variable-length instruction encodings?

    With a fixed length, as in AArch64 and RISC-V base instructions (32 bits each), the next instruction is always a known step away, so decoding is simple and parallel. Variable-length x86-64 instructions (1 to 15 bytes) pack common operations into fewer bytes, at the cost of a harder decoder.

    From the lesson Instruction encoding: fixed and variable

  77. How long can an x86-64 mov be?

    Its length depends on its operands and constants; the lesson's recordings show mov instructions of 2, 4, 5 and 7 bytes.

    From the lesson Instruction encoding: fixed and variable

  78. How many base instruction formats does RISC-V have, and what picks the format?

    Six: R, I, S, B, U and J. The opcode field decides the format, and the format decides how the bits above it split into registers and immediates, which makes hand decoding possible.

    From the lesson Instruction encoding: fixed and variable

  79. What instruction is the RISC-V word 0x05d00893?

    addi a7, zero, 93. The opcode bits pick the I-type format, the top 12 bits hold the immediate 93, rs1 is the zero register and rd is a7; the assembler writes the same instruction as li a7, 93.

    From the lesson Instruction encoding: fixed and variable

  80. What parts make up an x86-64 instruction?

    Optional prefixes, the opcode, an optional ModRM byte and SIB byte, then a displacement and an immediate. The 48 byte (REX.W, a 64-bit operand), the 66 operand-size prefix and the ModRM byte are the parts worth learning to spot.

    From the lesson Instruction encoding: fixed and variable

  81. What does a fixed 32-bit width cost, and how does RISC-V win some space back?

    A 64-bit constant or a far target does not fit in one instruction, so AArch64 builds such a constant with movz and movk. The RISC-V C extension adds 2-byte forms of common instructions, such as c.li a0, 10.

    From the lesson Instruction encoding: fixed and variable

  82. How does each ISA spell a plain register-to-register copy?

    mov on x86-64 and on AArch64. On RISC-V it is mv, a pseudo-instruction that the assembler writes as an addi with a zero offset.

    From the lesson Moving values and loading constants

  83. What is the usual way to set a register to zero on each ISA?

    xor eax, eax on x86-64, mov x0, xzr on AArch64 and li a0, 0 on RISC-V, which copies from the zero register. Each is shorter or cheaper than moving a literal zero.

    From the lesson Moving values and loading constants

  84. What is an immediate, and why can most instructions not carry a full 64-bit one?

    A constant stored inside the instruction's own encoding. A 4-byte AArch64 or RISC-V instruction must also hold its opcode and register fields, so there is no room for 64 bits of constant: movz and movk carry 16 bits each, addi 12 and lui 20.

    From the lesson Moving values and loading constants

  85. How many instructions does it take to load an arbitrary 64-bit constant on each ISA?

    One on x86-64, a 10-byte mov with a 64-bit immediate (movabs). Up to four on AArch64: a movz and then movk for each further 16-bit field. Up to eight on RISC-V, from the expansion of li.

    From the lesson Moving values and loading constants

  86. Apart from mov into a 64-bit register, what immediate do x86-64 instructions take?

    A 32-bit immediate that is sign-extended to 64 bits. A constant such as 0x100000000 needs 33 bits, so it is loaded into a register first and then used.

    From the lesson Moving values and loading constants

  87. Why does a hand-written lui for 0x80000000 give a negative number on RV64?

    lui sign-extends bit 31 of its result into the upper half of the 64-bit register, so the value becomes 0xffffffff80000000. Let li build such constants; it knows the trap.

    From the lesson Moving values and loading constants

  88. What does it mean that AArch64 and RISC-V are load/store architectures?

    Only loads and stores touch memory; every arithmetic or logic instruction works on registers. A value in memory is loaded, worked on and stored back, whereas x86-64 lets an instruction such as add rax, [rdi] read memory itself.

    From the lesson Loads and stores of every width

  89. Which two choices does every load make, and what goes wrong with the wrong one?

    A width (byte, halfword, word or doubleword) and an extension: zero for an unsigned value, sign for a signed one. Sign-extending the unsigned byte 0xFF turns 255 into -1.

    From the lesson Loads and stores of every width

  90. What do AArch64 ldp and stp do, and where are they seen most?

    They load or store two registers in one instruction. Almost every function prologue and epilogue uses them to save and restore registers; x86-64 and RISC-V have no general pair load.

    From the lesson Loads and stores of every width

  91. What is an aligned access, and why should portable code align its data?

    An access whose address is a multiple of its width, such as an 8-byte load from an address divisible by 8. Aligned-access instructions require it and some hardware traps on any misaligned access, so data is placed with directives such as .p2align 3.

    From the lesson Loads and stores of every width

  92. What does x86-64 gain from memory operands, and what do load/store ISAs gain by refusing them?

    x86-64 code is shorter, because one instruction can load and compute. Load/store code makes every memory access an explicit instruction and keeps encodings regular, which helps pipelines.

    From the lesson Loads and stores of every width

  93. Where does an assembler put read-only data, writable data and zero-initialised data?

    In .rodata, .data and .bss. .data is stored in the file with its values; .bss records only a size, takes no file space, and the loader hands it zeroed pages at start-up.

    From the lesson Static data and PC-relative addresses

  94. How does position-independent code form the address of a global on each ISA?

    From the program counter: one RIP-relative lea on x86-64, an adrp and add pair on AArch64, an auipc and addi pair on RISC-V. The distance to the data is known at link time even when the absolute address is not.

    From the lesson Static data and PC-relative addresses

  95. Why is modern code position-independent?

    So the loader may place it at any base address: address-space layout randomisation can then put it somewhere unpredictable, and a shared library can be mapped wherever there is room.

    From the lesson Static data and PC-relative addresses

  96. In NASM 64-bit code, what does default rel change?

    It makes a plain [label] operand RIP-relative instead of absolute, so the object can link into a position-independent executable. NASM 3.x deprecates the implicit absolute default.

    From the lesson Static data and PC-relative addresses

  97. What happens on macOS arm64 if code contains an absolute address?

    The link fails with a text-relocation error: the platform refuses code that the loader would have to patch with absolute addresses.

    From the lesson Static data and PC-relative addresses

  98. Why does RISC-V code depend on the gp register being set up?

    The linker may relax a PC-relative pair into a single gp-relative access to data near gp, so the code then works only if gp holds the value the linker assumed.

    From the lesson Static data and PC-relative addresses

  99. What is an addressing mode, and what is the effective address?

    An addressing mode is the rule an instruction uses to compute the memory address of an operand, such as a register plus an offset. The effective address is the address that rule finally produces, just before the access.

    From the lesson Addressing modes for arrays and structs

  100. How does one x86-64 operand reach a[i].y in an array of 8-byte structs?

    [base + index*8 + 4]: the base register holds the array, the index scaled by 8 steps to element i, and the displacement 4 is the offset of field y. One memory operand computes the whole address.

    From the lesson Addressing modes for arrays and structs

  101. What does x86-64 lea do with a memory operand?

    It computes the effective address and puts it in a register without touching memory, so it is also used for quick arithmetic such as a + b*4 + 8.

    From the lesson Addressing modes for arrays and structs

  102. What is the difference between AArch64 pre-index and post-index addressing?

    Pre-index, written with an exclamation mark after the brackets, adds the offset to the base register before the access and keeps the new value. Post-index, with the offset after the brackets, accesses the old address and then updates the base, which makes it the natural way to walk an array.

    From the lesson Addressing modes for arrays and structs

  103. RISC-V has only offset(base) addressing. How does it index an array of 8-byte elements?

    It shifts the index left by 3 with slli, adds the base, then loads at offset 0; with the Zba extension one sh3add does the shift and the add together.

    From the lesson Addressing modes for arrays and structs

  104. Which textbook addressing modes map onto real instructions of the three ISAs?

    Immediate, register, register indirect, indexed, base plus displacement, PC-relative and auto-increment all appear: auto-increment as AArch64 post-index, PC-relative as rip, adr and auipc forms.

    From the lesson Addressing modes for arrays and structs

  105. Why do x86-64, AArch64 and RISC-V have no separate signed add instruction?

    In two's complement a signed and an unsigned sum have the same bits, and the adder only works on bits. What differs is the overflow test, so one add serves both readings and the code checks the condition that matches its type.

    From the lesson Addition, subtraction and multiplication

  106. The 64-bit result 0x8000000000000000 came from INT64_MAX + 1. Is it right or wrong?

    It depends on the reading. As an unsigned number it is the correct sum, 2 to the power 63; as a signed number the addition wrapped to the most negative value. The overflow check you need follows from the reading.

    From the lesson Addition, subtraction and multiplication

  107. What does fixed-width integer arithmetic do with a result that does not fit?

    It keeps the low bits, so a 64-bit sum is exact modulo 2 to the power 64: 0xFFFFFFFFFFFFFFFF + 1 becomes 0.

    From the lesson Addition, subtraction and multiplication

  108. Why does every ISA have one mul but separate signed and unsigned high-half multiplies?

    Multiplying two 64-bit registers gives a low half that is identical for signed and unsigned operands, but a high half that is not. So one instruction gives the low half for both, and the high half needs a signed and an unsigned version.

    From the lesson Addition, subtraction and multiplication

  109. How does each ISA produce the full 128-bit product of two 64-bit registers?

    x86-64's one-operand mul or imul writes it to the pair rdx:rax. AArch64 pairs mul with umulh or smulh, and RISC-V pairs mul with mulhu, mulh or mulhsu, computing the high half first.

    From the lesson Addition, subtraction and multiplication

  110. How do compilers multiply by a small constant such as 5 or 9 without a multiply?

    They add shifted copies: lea with a scale on x86-64, a shifted register operand on AArch64, slli and add (or Zba's shNadd) on RISC-V. A product plus an addend is a single madd on AArch64.

    From the lesson Addition, subtraction and multiplication

  111. What are -7 / 2 and -7 % 2 in C on x86-64, AArch64 and RISC-V?

    -3 and -1 on all three. C rounds the quotient towards zero and the remainder takes the dividend's sign, and the signed divide instructions of all three ISAs round the same way. Python's -7 // 2 is -4, with remainder 1.

    From the lesson Division: traps, defined results, remainders

  112. Which registers do x86-64 div and idiv use, and what must come before each?

    They divide the 128-bit pair rdx:rax, leave the quotient in rax and the remainder in rdx. Before idiv, cqo copies the sign of rax into rdx; before div, xor edx, edx clears it. A wrong rdx gives a huge quotient or a trap.

    From the lesson Division: traps, defined results, remainders

  113. How does each ISA get the remainder of an integer division?

    x86-64 div and idiv leave it in rdx. AArch64 has no remainder instruction and computes it with msub, as dividend minus quotient times divisor. RISC-V has rem and remu, best placed straight after the matching divide.

    From the lesson Division: traps, defined results, remainders

  114. What happens on each ISA when a program divides by zero?

    x86-64 raises a divide error (#DE), and the process ends with SIGFPE, status 136 in the shell. AArch64 and RISC-V return a defined value instead and the program carries on. C leaves the case undefined, so check the divisor first.

    From the lesson Division: traps, defined results, remainders

  115. Why is INT64_MIN / -1 a special case?

    The true quotient, 2 to the power 63, does not fit in a signed 64-bit register. x86-64 traps on it as on a zero divisor, AArch64 and RISC-V return a defined result, and in C it is undefined behaviour.

    From the lesson Division: traps, defined results, remainders

  116. How does an optimising compiler divide a 32-bit int by 10 without a divide instruction?

    It multiplies by 0x66666667, shifts the product right by 34 and adds a fix for negative values, which is exact for every 32-bit input. When optimising for size (-Oz) it keeps the shorter divide.

    From the lesson Division: traps, defined results, remainders

  117. With a mask m that has one bit set, how do you set, clear, flip and test that bit?

    x | m sets it, x & ~m clears it, x ^ m flips it and x & m tests it. AArch64 has bic for the clear, and x86-64 test and AArch64 tst compute the AND only for the flags.

    From the lesson Bitwise logic, shifts and rotates

  118. What is the difference between a logical and an arithmetic right shift?

    A logical shift brings in zeros and divides an unsigned value by a power of two. An arithmetic shift brings in copies of the sign bit and divides a signed value, rounding towards minus infinity: -7 shifted right by 1 is -4. They are shr and sar, lsr and asr, srl and sra.

    From the lesson Bitwise logic, shifts and rotates

  119. Why does a compiler add 2^n - 1 to a negative value before replacing x / 2^n with a shift?

    An arithmetic shift rounds down, towards minus infinity, while C's division rounds towards zero. The bias makes the shift round the same way as the division for negative values.

    From the lesson Bitwise logic, shifts and rotates

  120. What happens when a shift count is equal to or larger than the register width?

    In a register, the count wraps at 64 or 32 on all three ISAs, and x86-64 also masks its 8-bit and 16-bit shifts to 5 bits. In C any count at or past the width is undefined behaviour, so keep counts in range.

    From the lesson Bitwise logic, shifts and rotates

  121. Which rotate instructions does each ISA have?

    x86-64 rotates both ways with rol and ror, AArch64 only to the right with ror, and base RISC-V not at all: rol and ror need Zbb. A rotate written in C with both counts masked compiles to the best of these.

    From the lesson Bitwise logic, shifts and rotates

  122. What is an AArch64 shifted register operand?

    A source register shifted before the instruction uses it, as in add x0, x1, x2, lsl 3, which adds x2 times 8 to x1. add, sub and the logical instructions accept it, which is why array indexing often costs no extra instruction.

    From the lesson Bitwise logic, shifts and rotates

  123. Which four facts do the flags of x86-64 and AArch64 record about a result?

    Whether it is zero (ZF, Z), its top bit (SF, N), whether an unsigned result did not fit (CF, C) and whether a signed result did not fit (OF, V). One add sets all four for both readings at once.

    From the lesson Flags and condition codes

  124. What is the difference between the carry flag and the overflow flag?

    The carry flag records a carry out of the top bit: the unsigned result did not fit. The overflow flag records that the signed result did not fit. UINT64_MAX + 1 sets only the carry; INT64_MAX + 1 sets only the overflow.

    From the lesson Flags and condition codes

  125. Which instructions write the flags on x86-64 and which on AArch64?

    On x86-64 nearly every arithmetic and logic instruction does, but not the moves, and inc and dec leave CF alone. On AArch64 only the s forms write them, such as adds and subs, with their aliases cmp, cmn and tst.

    From the lesson Flags and condition codes

  126. After a subtraction that borrows, what do x86-64 CF and AArch64 C hold?

    x86-64 sets CF = 1 for the borrow, while AArch64 sets C = 0, since its carry means no borrow. So jc and b.cs branch in opposite cases, but jb and b.lo, named for unsigned below, mean the same thing.

    From the lesson Flags and condition codes

  127. Why does a signed less-than test compare the sign flag with the overflow flag?

    When the subtraction a - b overflows, the stored sign bit is wrong. Testing SF against OF (N against V on AArch64) corrects for that: a is less than b exactly when the two differ.

    From the lesson Flags and condition codes

  128. How does RISC-V compare and check overflow without flags?

    slt and sltu write a comparison into a register as 0 or 1, and branches compare two registers directly. Overflow is an explicit sequence: one instruction for an unsigned sum and three for a signed one.

    From the lesson Flags and condition codes

  129. How do you turn a comparison into a 0 or 1 in a register on each ISA?

    setcc on x86-64 writes it to a byte register, cset on AArch64 writes 0 or 1 (csetm writes 0 or all ones), and RISC-V slt or sltu writes the comparison straight into a register.

    From the lesson Flags and condition codes

  130. How do you add two 128-bit numbers on a CPU with 64-bit registers?

    Split each into a low and a high 64-bit limb and add like digits on paper: low limbs first, then the high limbs plus the carry out of the low add. (2^64 - 1) + 1 gives a low limb of 0 with a carry, and a high limb of 1.

    From the lesson Carries and 128-bit arithmetic

  131. Which instructions chain a carry or borrow from limb to limb on x86-64 and AArch64?

    add then adc, and sub then sbb, on x86-64. adds then adc or adcs, and subs then sbc or sbcs, on AArch64. The borrow shows as CF = 1 on x86-64 and as C = 0 on AArch64.

    From the lesson Carries and 128-bit arithmetic

  132. RISC-V has no carry flag. How does it find the carry of s = a + b?

    With a compare: the sum wrapped exactly when s is below b, so sltu s, b gives the carry. A borrow of a - b is a below b, tested before the subtract. A limb with a carry coming in needs two compares and an or.

    From the lesson Carries and 128-bit arithmetic

  133. What must a loop over limbs keep between its iterations, and how does each ISA keep it?

    The carry from one limb to the next. x86-64 counts with inc and dec, which leave CF untouched; AArch64 counts with sub and cbnz, which leave the flags alone; RISC-V simply keeps the carry in a register.

    From the lesson Carries and 128-bit arithmetic

  134. How is the product of two multi-limb numbers built?

    As the sum of the partial products of every pair of limbs, each with a low and a high half placed at its own limb positions, plus carries between the columns. Results are checked against unsigned __int128 and Python's integers.

    From the lesson Carries and 128-bit arithmetic

  135. Why does a function that keeps only the low 128 bits of a 128 x 128-bit product need no carries?

    Everything that would need them lands above bit 127: the product of the two high limbs and the high halves of the cross products. Only the low products and the low halves of the cross terms remain.

    From the lesson Carries and 128-bit arithmetic

  136. Where are popcount, leading-zero and trailing-zero counts single instructions?

    Only where an extension provides them: POPCNT, LZCNT and BMI1 on x86-64, CSSC or the vector cnt on AArch64, and Zbb on RISC-V. The base ISAs offer bsr, bsf, clz and rbit at most.

    From the lesson Bit-manipulation instructions and extensions

  137. How does each ISA extract or insert a bit field?

    AArch64's base ISA has ubfx, sbfx and bfi. x86-64 BMI2 gathers and scatters bits under any mask with pext and pdep. Base RISC-V uses two shifts: left to drop the bits above, right to bring the field down.

    From the lesson Bit-manipulation instructions and extensions

  138. Why can the same C function compile to 2 instructions or to more than 20?

    A compiler uses an extension only when its target says the extension exists, and clang's macOS defaults, penryn and apple-m1, include neither POPCNT nor CSSC. The recorded pop64 took 20 instructions on x86-64 and 29 on rv64gc, but 2 with x86-64-v3 or rv64gc_zbb; apple-m1 needed 5, through the Advanced SIMD cnt and addv.

    From the lesson Bit-manipulation instructions and extensions

  139. What happens when a program runs an instruction the CPU does not implement?

    The CPU raises an exception (#UD on x86-64, an undefined or illegal instruction exception on AArch64 and RISC-V), the kernel delivers SIGILL, signal 4, and the shell reports status 132. Nothing falls back to a slower path.

    From the lesson Bit-manipulation instructions and extensions

  140. How can an unsupported instruction give a wrong answer instead of crashing?

    Some old encodings mean something else on older CPUs: on an x86-64 CPU without LZCNT, lzcnt runs as bsr and returns a different number with no error.

    From the lesson Bit-manipulation instructions and extensions

  141. How should a program decide whether it may use an ISA extension?

    Ask at run time and pick a code path once: CPUID on x86-64, the hwcaps or sysctl on AArch64, riscv_hwprobe on RISC-V Linux. A trial run that happens to work, as under Rosetta 2, proves nothing.

    From the lesson Bit-manipulation instructions and extensions

  142. How does a compiler turn if (cond) { body } into assembly?

    A compare, then a conditional branch on the opposite condition that jumps over the body. When the condition holds the branch falls through into the body; an else adds one unconditional jump at the end of the then-part, over the else-part.

    From the lesson Jumps and conditional branches

  143. One compare serves signed and unsigned values. What decides how its result is read?

    The condition of the branch that follows. jl, b.lt and blt treat the values as signed; jb, b.lo and bltu treat them as unsigned. Unsigned data needs the unsigned condition.

    From the lesson Jumps and conditional branches

  144. Why does one unsigned compare check an index at both ends?

    A negative index read as unsigned becomes a huge number, larger than any valid length, so i < n as an unsigned test rejects it along with every index that is too big. A signed test would let 0xffffffff through i < 100.

    From the lesson Jumps and conditional branches

  145. How do AArch64 and RISC-V branch on zero or on a single bit without a compare, and what does x86-64 need?

    AArch64's cbz and cbnz test a whole register and tbz and tbnz one bit, without touching the flags. RISC-V's beqz is beq against the zero register. x86-64 needs a test instruction to set the flags and then jz or jnz.

    From the lesson Jumps and conditional branches

  146. How far can a conditional branch reach on each ISA?

    Only as far as its offset field allows: 4 KiB either way for a RISC-V branch, 32 KiB for AArch64 tbz and 1 MiB for b.cond. An x86-64 near jump reaches about 2 GiB by using a longer encoding.

    From the lesson Jumps and conditional branches

  147. What happens when the target of a conditional branch is out of range?

    The branch is relaxed: the opposite test jumps over an unconditional jump, which reaches further. LLVM's RISC-V assembler does this on its own, x86-64 just picks the longer encoding, and AArch64 code must be written that way by hand.

    From the lesson Jumps and conditional branches

  148. What four parts does every compiled loop have?

    A label, a test, a body and a branch back to the label. A while, a do-while and a for loop all reduce to the same parts; the test decides which iteration is the last.

    From the lesson Loops: while, for and counting down

  149. Why do loops that count down to zero often need no compare instruction?

    The decrement itself sets the zero flag: subs then b.ne on AArch64, dec then jnz on x86-64. RISC-V has no flags but branches on the counter register directly with bnez.

    From the lesson Loops: while, for and counting down

  150. Why does a count-down loop need a guard for n = 0?

    Its first decrement would turn 0 into the largest unsigned value, and the loop would run billions of times instead of not at all. The guard skips the loop when there is nothing to do.

    From the lesson Loops: while, for and counting down

  151. How does writing the loop invariant first prevent off-by-one errors?

    It fixes what each variable means. For strlen, every byte of [0, len) is non-zero says len counts checked bytes, so the body must load str[len] first. When the loop stops, the invariant plus the failed test give the result.

    From the lesson Loops: while, for and counting down

  152. What is loop rotation, and why does -O2 code do it?

    The compiler moves the test to the bottom of the loop, so each iteration runs one branch instead of two, and puts a guard in front so that a loop that should run zero times does not run once.

    From the lesson Loops: while, for and counting down

  153. How does each ISA pick one of two registers under a condition without a branch?

    cmov on x86-64 and csel on AArch64 test the flags a compare left. RISC-V, with the Zicond extension, joins two czero instructions with an or. No branch stands between the compare and the result.

    From the lesson Branchless code: cmov, csel and Zicond

  154. How does a cmov differ from a conditional jump in what it costs the pipeline?

    A conditional jump is guessed: a right guess costs almost nothing, a wrong one throws away the work done after it, about 10 to 20 cycles on current cores. A cmov has nothing to guess, but both inputs must be computed on every call.

    From the lesson Branchless code: cmov, csel and Zicond

  155. When does branchless code win, and when does it lose?

    It wins on data the predictor cannot learn, such as shuffled or near half-and-half input, because it never pays a misprediction. It loses on predictable data, such as sorted input or a rarely set flag, because it always pays for both sides.

    From the lesson Branchless code: cmov, csel and Zicond

  156. What are the two RISC-V shapes of a branchless max, with and without Zicond?

    With Zicond, four instructions: slt for the condition, two czero and an or. Without it, a five-instruction mask from the base ISA: slt, a subtract from zero, xor, and, then xor again.

    From the lesson Branchless code: cmov, csel and Zicond

  157. How do you make sure the compiler really kept a branchless select?

    Read the disassembly. The select shows under its own name, such as cmovge, csel or czero.nez; a conditional branch in its place means the compiler chose to branch.

    From the lesson Branchless code: cmov, csel and Zicond

  158. What does a dense switch statement compile to?

    One bounds check, one load from a table, one add and one indirect branch. The table lives in read-only data, next to the string literals.

    From the lesson Switch statements and jump tables

  159. Why are jump-table entries offsets rather than addresses, and how big are they?

    An offset from a fixed point in the same image stays correct wherever the image is loaded, so the linker resolves every entry and the loader patches none. Entries are 4 bytes on x86-64 and RISC-V and can be 1 byte on AArch64, so sixteen cases cost 64, 64 and 16 bytes.

    From the lesson Switch statements and jump tables

  160. What decides whether a compiler builds a jump table or a tree of compares?

    The range of the case values, not how many there are. Spread-out values would leave a table mostly empty, so the lesson's sparse switch became 23 compares and no table, and a target's own threshold can rule out a table even for six dense cases.

    From the lesson Switch statements and jump tables

  161. Why is a jump table quicker than a tree of compares for many dense cases?

    The table costs the same few instructions for every value, while a tree costs one conditional branch per level, four or five for sixteen cases, and each of them can be mispredicted.

    From the lesson Switch statements and jump tables

  162. Which instructions mark legal targets of indirect branches under control-flow integrity?

    endbr64 on x86-64, bti c and bti j on AArch64, and lpad on RISC-V. A processor enforcing the check faults when an indirect branch lands anywhere else.

    From the lesson Switch statements and jump tables

  163. How does each ISA let a compiler's own jump table get through the landing-pad check?

    On x86-64 a no-track prefix exempts that indirect jump. On AArch64 every landing gets a bti j, which satisfies the check. On RISC-V a jump through register x7 is exempt.

    From the lesson Switch statements and jump tables

  164. Which way does the stack grow on x86-64, AArch64 and RISC-V, and what does the stack pointer address?

    Towards lower addresses on all three. The stack pointer holds the address of the value stored most recently, so the stack works last in, first out.

    From the lesson The stack: push, pop and red zones

  165. What exactly do x86-64 push and pop do to rsp and to memory?

    push lowers rsp by 8 and then stores its operand at the new address; pop loads the value at rsp and then raises rsp by 8. Registers therefore come back in the reverse of the order they were pushed.

    From the lesson The stack: push, pop and red zones

  166. AArch64 and RISC-V have no push instruction. How do they save registers on the stack?

    AArch64 uses stp with pre-index writeback, which lowers sp by 16 and stores a pair in one instruction, and ldp with post-index to undo it. RISC-V lowers sp with addi and stores each register at its own offset with sd.

    From the lesson The stack: push, pop and red zones

  167. Why did a function that moved sp by only 8 bytes crash on Apple silicon?

    AArch64 and RISC-V keep sp a multiple of 16, and on AArch64 a load or store through a misaligned sp faults. The function's next access through sp ended with a bus error.

    From the lesson The stack: push, pop and red zones

  168. What is the red zone, and which ABIs have one?

    The 128 bytes just below the stack pointer that signal and interrupt handlers leave alone, so a leaf may keep data there without moving the stack pointer. System V on x86-64 and Apple on arm64 promise it; Microsoft x64, AAPCS64 and RISC-V do not.

    From the lesson The stack: push, pop and red zones

  169. Why is the x86-64 Linux kernel built with -mno-red-zone?

    An interrupt taken in kernel code stays at the same privilege level, so the processor pushes its frame onto the current stack just below rsp, exactly where red-zone data would sit. Kernel functions therefore lower rsp before storing anything.

    From the lesson The stack: push, pop and red zones

  170. After a pop, what happens to the bytes that held the value?

    Nothing at once: they stay in memory below the stack pointer until a later push, call or interrupt overwrites them.

    From the lesson The stack: push, pop and red zones

  171. Where does a call put the return address on each ISA?

    x86-64 call pushes it onto the stack. AArch64 bl and blr write it to x30, and RISC-V jal and call write it to ra. ret returns through the same place.

    From the lesson Calls, returns and the link register

  172. Why must an AArch64 or RISC-V function that calls another save its link register first?

    Its own call overwrites x30 or ra with a new return address. Without a saved copy, the function's final ret goes back to the wrong place; the lesson's broken greet returned to its own ret and never came back.

    From the lesson Calls, returns and the link register

  173. x86-64 calls never overwrite the caller's return address. What can still break the return?

    ret pops whatever word is on top of the stack. Every push and every sub from rsp must be undone before ret, or it jumps to whatever value was left there.

    From the lesson Calls, returns and the link register

  174. What does a leaf function at -O2 need on the stack?

    Often nothing at all. Nothing overwrites its link register while it runs, so AArch64 and RISC-V leaves need no save and no stack instructions; an x86-64 leaf already has its return address on the stack and leaves it there.

    From the lesson Calls, returns and the link register

  175. What is a tail call, and how is it compiled?

    A call that is the last thing a function does. It becomes a plain jump (b, jmp or RISC-V tail), so the callee returns straight to the original caller and no frame is kept for the middle function.

    From the lesson Calls, returns and the link register

  176. How far can a call reach, and what do linkers do about distant targets?

    About 1 MiB for jal, 128 MiB for bl and about 2 GiB for an x86-64 call or a RISC-V auipc and jalr pair. A RISC-V linker shrinks a pair whose target is near into one jal; an AArch64 linker sends a bl that cannot reach through a veneer that may change x16 and x17.

    From the lesson Calls, returns and the link register

  177. What does a calling convention fix, and why does it matter?

    Which registers carry arguments and results, where extra arguments go on the stack, which registers a callee must preserve and how the stack is aligned at a call. A caller and a callee built separately share nothing else.

    From the lesson Calling conventions: arguments and returns

  178. Which registers carry the first integer or pointer arguments under each ABI?

    rdi, rsi, rdx, rcx, r8 and r9 on System V; rcx, rdx, r8 and r9 on Microsoft x64; x0 to x7 on AAPCS64; a0 to a7 on RISC-V.

    From the lesson Calling conventions: arguments and returns

  179. Where does the first argument that does not fit in registers go?

    The seventh integer argument is at [rsp + 8] on System V at entry; on Windows the fifth sits at [rsp + 40], above the 32 bytes of shadow space; on AArch64 and RISC-V the ninth is at [sp].

    From the lesson Calling conventions: arguments and returns

  180. What is shadow space in the Microsoft x64 convention?

    32 bytes that every caller reserves on the stack just above the return address, so the callee may store rcx, rdx, r8 and r9 there if it wants to.

    From the lesson Calling conventions: arguments and returns

  181. How do the ABIs count floating-point arguments?

    All of them pass floating-point values in their own registers. System V, AAPCS64 and RISC-V count integers and floating-point values separately; Microsoft x64 counts positions, so the third argument always uses the third register of its kind and the other kind's third register goes unused.

    From the lesson Calling conventions: arguments and returns

  182. Where do function results come back?

    One integer in rax, x0 or a0, and a 16-byte pair in two registers except on Windows. A larger result goes to caller memory whose address arrives in rdi, rcx, x8 or a0.

    From the lesson Calling conventions: arguments and returns

  183. How does Apple's arm64 ABI differ from AAPCS64 for narrow arguments?

    The caller, not the callee, widens a value narrower than 32 bits, and a stack argument takes only its own size instead of a whole 8-byte slot. Apple and Arm Linux code therefore disagree about one-byte arguments.

    From the lesson Calling conventions: arguments and returns

  184. What is the difference between the LP64 and LLP64 data models?

    LP64, used by 64-bit Linux and macOS, makes long and pointers 64 bits. LLP64, used by 64-bit Windows, keeps long at 32 bits and makes long long and pointers 64.

    From the lesson Calling conventions: arguments and returns

  185. Who preserves a callee-saved register, and who a caller-saved one?

    A callee-saved register must hold the same value on return, so a function that writes one saves and restores it. A caller-saved register may change in any call, so a caller that still needs the value saves it or keeps it elsewhere.

    From the lesson Callee-saved registers and stack frames

  186. Which general registers are callee-saved under System V, AAPCS64 and RISC-V?

    rbx, rbp and r12 to r15 under System V; x19 to x29 under AAPCS64, plus the bottom 64-bit halves of vector registers v8 to v15; s0 to s11 on RISC-V, plus fs0 to fs11. Microsoft x64 adds rdi, rsi and xmm6 to xmm15 to the System V set.

    From the lesson Callee-saved registers and stack frames

  187. Why does a missed restore of a callee-saved register so often go unnoticed, and what catches it?

    The function still returns the right result; only its caller, later, finds a wrong value. A harness that fills every callee-saved register, makes one call and compares them all afterwards catches it at once.

    From the lesson Callee-saved registers and stack frames

  188. What must a prologue save, and what must the epilogue undo?

    The prologue saves every callee-saved register the body will write, and the link register if the body makes calls, before anything overwrites them. The epilogue reloads each saved value, pops in reverse order, and puts the stack pointer back where it was on entry.

    From the lesson Callee-saved registers and stack frames

  189. What is a frame record, and what links the records together?

    Two 64-bit values in a frame: the caller's frame-record address and the return address. The frame pointer, x29 on AArch64 or rbp on x86-64, points at the record, so a debugger or profiler can walk every active call.

    From the lesson Callee-saved registers and stack frames

  190. What does a function that calls others without building a frame record do to a stack walk?

    It does not drop itself from the walk, it drops its caller. Apple requires x29 to address a valid record at all times, while GCC omits the frame pointer by default from -O1.

    From the lesson Callee-saved registers and stack frames

  191. What alignment must the stack pointer have at a call, and why is an x86-64 function's entry 8 bytes off?

    A multiple of 16 under System V, Microsoft x64, AAPCS64 and RISC-V. On x86-64 the call itself pushes an 8-byte return address, so a function must move rsp by an odd multiple of 8 before it calls again.

    From the lesson Stack alignment and the crashes it causes

  192. Why can a System V function that pushes exactly one register call another at once?

    The return address took 8 bytes and the push another 8, which makes 16, so rsp is a multiple of 16 again. With zero or two pushes the function needs 8 more bytes, often a push rax or a sub rsp, 8.

    From the lesson Stack alignment and the crashes it causes

  193. On AArch64, where does a misaligned stack pointer fault?

    At the first memory access whose base register is sp, often the callee's first frame store, with SIGBUS and BUS_ADRALN. The arithmetic that misaligned sp is never checked, so the crash points at correct code.

    From the lesson Stack alignment and the crashes it causes

  194. Why can a misaligned stack pass tests on x86-64 and then crash later?

    x86-64 faults only on instructions that need aligned memory, such as movaps. Integer code runs fine until a callee, typically a variadic one saving its vector registers, uses an aligned vector store.

    From the lesson Stack alignment and the crashes it causes

  195. How do you keep hand-written functions aligned?

    Count: start at 8 on x86-64 for the return address, add every push and every subtraction, include Windows' 32 bytes of shadow space, and make each call see a multiple of 16. On AArch64 and RISC-V round every frame up to 16.

    From the lesson Stack alignment and the crashes it causes

  196. How does a function get 32-byte alignment for a local when the ABI only promises 16?

    It rounds the stack pointer down with an and, such as and rsp, -32, and keeps the old value in a frame pointer to restore it, because the rounding loses it.

    From the lesson Stack alignment and the crashes it causes

  197. What does each level of a recursion keep for itself?

    Its own return address and its own copy of every value it needs after the recursive call, kept in a callee-saved register (after saving the caller's copy) or in a stack slot. A value left in a caller-saved register is lost at the next call.

    From the lesson Recursion in assembly

  198. fib(20) made 21,891 calls. How deep did its stack get?

    No more than 20 levels were active at once. The number of calls sets the running time; the depth sets the stack the recursion needs.

    From the lesson Recursion in assembly

  199. How do you tell whether a recursion fits in its stack?

    Multiply the frame size by the depth. With 64-byte frames a 1 MiB thread reached depth 16,574; the next frame hit the guard page and a signal stopped the program.

    From the lesson Recursion in assembly

  200. Why can a tail-recursive function run in constant stack space?

    Nothing is left to do after its recursive call, so it needs no frame afterwards: the call becomes a jump back to the start with new arguments, which is a loop. The lesson's sum_tail ran 100,000 levels where sum_rec overflowed.

    From the lesson Recursion in assembly

  201. From which optimisation level do clang and GCC turn tail recursion into a loop?

    clang from -O1 and GCC from -O2. A debug build at -O0 still pays a full frame for every level.

    From the lesson Recursion in assembly

  202. What is a base case, and what happens without one?

    The case a recursive function answers without calling itself, such as fact(0) = 1. Without one the calls go on until the stack runs out and the guard page stops the program.

    From the lesson Recursion in assembly

  203. How does System V decide where a struct argument goes?

    It cuts the struct into eightbytes and classifies each: an INTEGER eightbyte goes in the next general register, an SSE one in the next vector register. A struct of more than two eightbytes is MEMORY and is copied onto the stack.

    From the lesson Structs, variadic calls and large values

  204. Which structs does Microsoft x64 pass in a register?

    Only those that are exactly 1, 2, 4 or 8 bytes long. Every other struct goes as the address of a copy.

    From the lesson Structs, variadic calls and large values

  205. What is a homogeneous floating-point aggregate, and how does AAPCS64 pass one?

    A struct or array of one to four members of the same floating-point type. It travels in that many floating-point registers whatever its size, so three doubles (24 bytes) arrive in d0 to d2 and come back the same way.

    From the lesson Structs, variadic calls and large values

  206. When a large struct is passed by reference, who owns the memory?

    The caller makes a copy and passes its address, and the callee owns that copy and may write to it. A caller that passes the address of its own struct instead lets the callee change it.

    From the lesson Structs, variadic calls and large values

  207. How does RISC-V pass structs?

    One or two fields may get registers of their own kind, floating-point fields in floating-point registers; anything over 16 bytes goes by reference, as the address of a copy.

    From the lesson Structs, variadic calls and large values

  208. Where do variadic arguments go on each platform?

    Linux x86-64 uses the named-argument registers and puts the number of vector registers used in al; Linux AArch64 uses the named registers; Windows puts a variadic double in an integer register as well; RISC-V only in an integer register; Apple arm64 gives every variadic argument its own 8-byte stack slot.

    From the lesson Structs, variadic calls and large values

  209. Why does a variadic call written for Linux AArch64 print other values on Apple silicon?

    The two va_list types walk different places. Apple's is a pointer that steps through 8-byte stack slots; Linux AArch64's is a 32-byte struct that reads two register save areas first. The callee looks where the caller put nothing.

    From the lesson Structs, variadic calls and large values

  210. How does a program make a Linux system call on each of the three ISAs?

    It loads the call number into a register and traps: rax and syscall on x86-64, x8 and svc 0 on AArch64, a7 and ecall on RISC-V. Up to six arguments go in registers, and the result comes back in rax, x0 or a0.

    From the lesson Linux system calls on three ISAs

  211. Why does the fourth system-call argument go in r10 on x86-64 instead of rcx?

    The syscall instruction itself overwrites rcx with the return address and r11 with the flags, so the kernel's convention moves the fourth argument to r10.

    From the lesson Linux system calls on three ISAs

  212. Why is write number 1 on x86-64 but 64 on AArch64 and RISC-V?

    Each architecture numbers its calls in its own kernel table. x86-64 has its own syscall_64.tbl (write 1, exit_group 231); AArch64 and RISC-V share the generic table (write 64, exit_group 94), which has openat but no open.

    From the lesson Linux system calls on three ISAs

  213. How does a raw Linux system call report failure, and how should code test for it?

    The result register holds a negative error number, a value from -4095 to -1: writing to a closed descriptor returns -9, which is -EBADF. Test that range with one unsigned compare; never compare with -1.

    From the lesson Linux system calls on three ISAs

  214. What does the C library wrapper do when a system call fails?

    It stores the positive error number in the per-thread errno, such as 9 for EBADF, and returns -1, the convention C programs check. errno means something only after a failure.

    From the lesson Linux system calls on three ISAs

  215. What is the difference between the exit and exit_group system calls?

    exit ends only the calling thread; exit_group ends every thread of the process, and it is the call a C program makes when it exits.

    From the lesson Linux system calls on three ISAs

  216. Why can read() return fewer bytes than you asked for?

    It returns what is available now, up to the request, and a short count is not an error. A 10,000-byte file read 4,096 at a time came back as 4,096, 4,096 and 1,808; a pipe or socket hands over whatever has arrived so far.

    From the lesson Reading input safely with read()

  217. What does read() returning 0 mean?

    End of file: no more bytes will come from that descriptor, because the file has been read to its end or every writer of the pipe has closed it.

    From the lesson Reading input safely with read()

  218. How should code read exactly n bytes?

    Loop: add each count to what it has, ask again for the rest, retry when a read fails with EINTR, and treat a 0 before n bytes as a truncated record.

    From the lesson Reading input safely with read()

  219. How does a line reader avoid writing past its buffer?

    It checks the length before every store, so no line can go past the end, and it reads a too-long line to its end so the caller can reject it instead of seeing the rest as the next line.

    From the lesson Reading input safely with read()

  220. How do you validate the content of input, and how cheap is each range test?

    With an allowlist: accept only the bytes you expect and reject everything else. Each range is one subtraction and one unsigned compare, because a byte below the range wraps to a large unsigned value.

    From the lesson Reading input safely with read()

  221. How must a decimal parser test each step of value * 10 + digit?

    Check both steps for overflow: the multiply (umulh on AArch64 gives the high half) and the carry after the add. Without the checks, 18,446,744,073,709,551,616 parses as 0 instead of an error.

    From the lesson Reading input safely with read()

  222. What is on the stack when a Linux program reaches _start?

    At the stack pointer is argc, then argc pointers to the argument strings and a NULL, then the environment pointers and another NULL, then the auxiliary vector's type and value pairs. The strings sit above, and there is no return address.

    From the lesson Process startup: argc, argv, envp and auxv

  223. A program without libc wants argv and envp. Where are they on x86-64 Linux?

    argv starts at rsp + 8, just above argc, and envp starts at rsp + 8 * argc + 16, after the argument pointers and their NULL. The auxiliary vector begins after the environment's NULL.

    From the lesson Process startup: argc, argv, envp and auxv

  224. What does the auxiliary vector carry, and how does it end?

    Facts from the kernel as type and value pairs, such as AT_PAGESZ (the page size), AT_HWCAP, AT_RANDOM and AT_ENTRY. A pair of type AT_NULL ends it, so a program can find the page size without a system call.

    From the lesson Process startup: argc, argv, envp and auxv

  225. Why must _start end with an exit system call instead of ret?

    The kernel jumped to the entry point rather than calling it, so there is no return address on the stack to return to.

    From the lesson Process startup: argc, argv, envp and auxv

  226. How does macOS start a process differently from Linux?

    It builds the same kind of block, with apple strings in place of the auxiliary vector, but dyld runs first and hands main pointers into it. In the recorded run the word just below argv[0] was argc, 4.

    From the lesson Process startup: argc, argv, envp and auxv

  227. In a linked C program, what runs between the kernel and main?

    crt1.o's _start reads the initial stack and calls __libc_start_main, which prepares the C library, calls main(argc, argv, envp) and hands main's return value to exit.

    From the lesson Process startup: argc, argv, envp and auxv

  228. How much memory does mmap hand out for a request of 100,000 bytes on Apple silicon?

    Whole pages at a page-aligned address: seven 16 KiB pages, 114,688 bytes, all zero to start with. munmap likewise gives back whole pages from a page-aligned address.

    From the lesson Getting memory from the OS: brk and mmap

  229. Why does a 1-byte mmap still cost a whole page?

    Page tables translate and guard memory a page at a time, so the kernel rounds every length up to whole pages. That is why programs get large mappings and let an allocator hand out the bytes.

    From the lesson Getting memory from the OS: brk and mmap

  230. How do Linux and macOS assembly code get anonymous memory?

    On Linux with the mmap system call: six arguments with the flags in r10 on x86-64, number 9 there and 222 on arm64 and RISC-V. macOS code calls mmap in libSystem, and spells MAP_ANON as 0x1000 instead of Linux's 0x20.

    From the lesson Getting memory from the OS: brk and mmap

  231. How does a bump allocator serve a request, and how does it give memory back?

    It rounds its offset up to the alignment (an add and a bic on AArch64), checks the new end against the mapping and moves the offset past the piece. It never frees one piece; one munmap releases the whole region.

    From the lesson Getting memory from the OS: brk and mmap

  232. What do brk and sbrk move, and how does glibc's malloc use brk and mmap?

    They move the program break, the end of the heap. glibc's malloc serves small blocks from that heap and uses separate mmap regions above a threshold that starts at 128 KiB. macOS has no brk call at all.

    From the lesson Getting memory from the OS: brk and mmap

  233. What happens when a program writes to a page that does not allow it?

    It stops with a signal. On macOS a write to a read-only page gives SIGBUS (status 138) and to an unmapped page SIGSEGV (139); on Linux both give SIGSEGV. mprotect is the call that changes a page's permissions.

    From the lesson Getting memory from the OS: brk and mmap

  234. Which interface does each operating system promise to keep stable?

    Linux promises its system calls. Apple promises libSystem, and Microsoft promises the documented Win32 functions in DLLs such as kernel32.dll, not their system-call numbers.

    From the lesson macOS and Windows: calling the system safely

  235. How does macOS assembly call write?

    Like any C function: bl _write with the arguments in x0 to x2, the leading underscore on the name, adrp and add for addresses, and x29 and x30 saved. After a -1 it reads errno through ___error.

    From the lesson macOS and Windows: calling the system safely

  236. What does libSystem's write stub do, and why is calling the trap yourself a bad idea?

    It loads 4 into x16, runs svc 0x80 and turns a set carry flag into -1 and errno. The raw trap works on today's release, but Apple supports no programs that rely on the system-call interface, so a later release may change it.

    From the lesson macOS and Windows: calling the system safely

  237. How does a failed call look on each route?

    A negative error number on a Linux system call, the error number with the carry flag set on the macOS trap, -1 with errno from the C library, and FALSE with GetLastError from Win32.

    From the lesson macOS and Windows: calling the system safely

  238. What does a Windows x64 call into kernel32 need?

    Four arguments in rcx, rdx, r8 and r9, 32 bytes of shadow space with further arguments above it, and a 16-byte aligned rsp when the call executes. The call goes through a thunk and an import address table slot that the loader fills.

    From the lesson macOS and Windows: calling the system safely

  239. What is an import library on Windows?

    A .lib file that tells the linker which DLL exports each function, so a program can link against kernel32.dll without its code. llvm-dlltool can make one from a .def file.

    From the lesson macOS and Windows: calling the system safely

  240. Your assembly main calls puts. Why does linking the object with ld alone fail while clang links it?

    The object only names puts; ld resolves names against the files and libraries on its command line, and the assembly brings none. clang is a driver: it runs ld with the C runtime added, -lSystem on macOS, or the start-up files and -lc on Linux.

    From the lesson Calling C library functions from assembly

  241. When clang links an assembly object, who calls _main, and what happens to its result?

    The C runtime calls _main as an ordinary C function and hands its return value to exit, so main may simply return.

    From the lesson Calling C library functions from assembly

  242. What must assembly do to call a C function on AArch64?

    Put the arguments in x0 to x7 as the prototype orders them, keep sp a multiple of 16 at the call, and hold every value that must survive the call in a callee-saved register from x19 to x28.

    From the lesson Calling C library functions from assembly

  243. Where do printf's variadic arguments go on Apple arm64, on AArch64 Linux and on x86-64 Linux?

    On Apple arm64 each one in its own 8-byte stack slot. On AArch64 Linux in the next argument register. On x86-64 Linux in the next register too, with al saying how many vector registers carry arguments.

    From the lesson Calling C library functions from assembly

  244. What must assembly do with the pointers that malloc and qsort exchange?

    Test malloc's result for NULL before the first use. For qsort, pass the comparator's address in x3; qsort calls it back with the addresses of two elements to compare.

    From the lesson Calling C library functions from assembly

  245. How does a call from your program reach a function in a shared library?

    Through code in your own program. On macOS a stub loads the function's address from a __got slot that dyld binds at load time. On Linux a PLT entry makes an indirect jump via its GOT slot, which the dynamic linker fills at the first call, or at start-up when LD_BIND_NOW is set.

    From the lesson Calling C library functions from assembly

  246. What must an assembly function do so that C can call it through a prototype?

    Look like a compiled C function from outside: export its name as a global symbol (with the leading underscore on macOS), read the arguments where the convention puts the prototype's parameters, leave the result in x0 or rax, and keep every callee-saved register and sp as it found them.

    From the lesson Writing assembly functions that C can call

  247. How does one prototype, long f(long a, long b), map onto the three ABIs?

    By position: a and b arrive in x0 and x1 on AAPCS64, in rdi and rsi on x86-64 System V, and in a0 and a1 on RISC-V.

    From the lesson Writing assembly functions that C can call

  248. Which registers must a small assembly function that calls nothing save?

    None, if it uses only scratch registers. Any callee-saved register it does touch must be saved before it is written and restored before ret.

    From the lesson Writing assembly functions that C can call

  249. How do you test an assembly function and check that C can link against it?

    A C harness compares the function with a plain C version on every case, the empty input included, and nm shows whether the name is exported as a global symbol.

    From the lesson Writing assembly functions that C can call

  250. What makes an assembly label visible to code in other object files?

    Declaring it global: .globl in GNU-style assemblers, global in NASM. Without it the name stays local to its own object and the linker cannot match it.

    From the lesson Writing assembly functions that C can call

  251. What does the compiler know about the instructions inside a GCC or Clang extended asm statement?

    Only what the operand lists say. It fills each operand into the template and hands the text to the assembler without reading it, so anything the instructions change that the lists leave out goes unseen by the optimiser.

    From the lesson Inline assembly in C and Rust

  252. What do the constraint strings "r", "m", "=r" and "+r" mean in extended asm?

    "r" puts an operand in a general register and "m" in memory. An = marks an output that is only written, and a + an operand that is read and written.

    From the lesson Inline assembly in C and Rust

  253. What is a clobber in an extended asm statement?

    An entry in the last list that names something else the template changes, such as a register or memory, so the compiler keeps none of its own values there across the statement.

    From the lesson Inline assembly in C and Rust

  254. When does an asm output need the & of "=&r"?

    When the template writes the output before it has finished reading its inputs. The & makes the compiler give the output a register that no input uses.

    From the lesson Inline assembly in C and Rust

  255. Why do the registers inside the same asm statement differ between -O0 and -O2 builds?

    The compiler picks the registers for each operand to suit the code around the statement, and that code changes with the optimisation level.

    From the lesson Inline assembly in C and Rust

  256. Which compilers accept inline assembly in C, and what does Rust use?

    GCC and Clang accept it; MSVC does not for x64 or ARM64 targets. Rust has its own asm! macro, and most code needs neither.

    From the lesson Inline assembly in C and Rust

  257. What does a relocation entry name, and what decides the value written?

    A field to patch, a relocation type and a symbol with an addend. The type's formula, such as S + A - P, decides what the linker writes into the field.

    From the lesson Linking, relocations and PIE in depth

  258. What does R_X86_64_PC32 ask the linker to write, and why is it safe in a PIE?

    S + A - P: the symbol's address plus the addend minus the field's own address, a 32-bit distance. Code and data move together in a PIE, so the distance stays right at any load address and the linker can finish the field.

    From the lesson Linking, relocations and PIE in depth

  259. Why does a PIE link refuse R_X86_64_32S, and how does NASM avoid it?

    32S asks for the symbol's absolute address in 32 signed bits, which depends on where the loader puts the file, so ld.lld refuses it and suggests -fPIC. default rel makes NASM write a RIP-relative operand and a PC32 record instead.

    From the lesson Linking, relocations and PIE in depth

  260. What is the addend of a relocation?

    A constant stored in the relocation that the linker adds to the symbol's address, the A of the formulas in the processor ABIs, as in S + A - P.

    From the lesson Linking, relocations and PIE in depth

  261. ld.lld finished the PC32 field of a lea with the bytes 19 21 00 00. What value is that, and why could the linker finish it?

    The distance 0x2119, stored little-endian, which the formula S + A - P gave. The code and its data move together in a PIE, so that distance is final at link time and nothing is left for the loader to patch.

    From the lesson Linking, relocations and PIE in depth

  262. What three rules give a C struct its layout?

    Members keep their declared order, each starts at the next multiple of its own alignment, and the total size rounds up to the largest alignment, so an array of the struct keeps every member aligned too.

    From the lesson Structs, arrays and linked lists in assembly

  263. Why does adding up a struct's member sizes give 28 when sizeof says 32?

    The compiler inserted padding: 4 unused bytes before the 8-byte member at offset 24, so it starts on a multiple of 8. Padding is part of every record.

    From the lesson Structs, arrays and linked lists in assembly

  264. How does machine code read a struct field, and what must match?

    It keeps no field names, only offsets: one load at base plus the field's offset, such as 16 for the marks field. The width of the load must match the width of the field.

    From the lesson Structs, arrays and linked lists in assembly

  265. Why ask the compiler for sizeof and offsetof instead of counting bytes by hand?

    The compiler applies the ABI's layout rules exactly, padding included, and those numbers are what array strides and hand-written field loads must use.

    From the lesson Structs, arrays and linked lists in assembly

  266. Where does element i of an array of structs start?

    At the base plus i times the stride, and the stride is the struct's size with its padding included.

    From the lesson Structs, arrays and linked lists in assembly

  267. What does an assembler do with a macro?

    It pastes the macro's body in at every use, replacing each parameter with the text of that use's argument, before it assembles anything. There is no call, no return and no frame.

  268. What does a macro cost compared with a function?

    Bytes at every use instead of a call: three uses of WRITE_STR put 15 of main's 20 instructions into the object, and no symbol of that name existed. A function exists once and costs a call and a return each time.

  269. How do NASM and GNU-style assemblers write a macro and its parameters?

    NASM uses %macro NAME count with %1, %2 and so on, ending with %endmacro. GNU-style assemblers use .macro name params with \param inside, ending with .endm. nasm -E and a disassembly show what an expansion produced.

  270. When does a macro suit better than a function, and when not?

    Macros suit short sequences used often and jobs only text can do, such as a different string at each use. Functions suit longer code, code that C calls, and code a debugger should see as a symbol.

  271. What does conditional assembly decide, and when?

    Which lines of the source exist at all, decided before anything is assembled, with %if or %ifdef in NASM and .if or .ifdef in GNU-style assemblers. It lets one source keep a different block for each target.

  272. Where does each ISA keep scalar doubles, and how is a double add spelled?

    In a register file of their own: the SSE registers xmm0 to xmm15 on x86-64 (addsd), d0 to d31, the low 64 bits of v0 to v31, on AArch64 (fadd), and f0 to f31 on RISC-V (fadd.d).

    From the lesson Scalar floating-point arithmetic

  273. What is the difference between fmadd and an fmul followed by an fadd?

    fmadd rounds once, at the end, so the add sees the exact product; fmul then fadd rounds twice and loses the small bits. 0.1 * 10 - 1 gives 0 unfused but 5.55e-17 fused, the exact error of the stored 0.1.

    From the lesson Scalar floating-point arithmetic

  274. Where do the arguments of double scale(int64_t n, double x, double y) go under each ABI?

    rdi, xmm0 and xmm1 on System V; rcx, xmm1 and xmm2 on Microsoft x64, which fills slots by position; x0, d0 and d1 on AAPCS64; a0, fa0 and fa1 on RISC-V LP64D.

    From the lesson Scalar floating-point arithmetic

  275. Converting a double to an integer must round. How does each ISA choose the rule?

    x86-64 cvttsd2si truncates. AArch64 bakes five rules into separate opcodes: fcvtzs, fcvtns, fcvtas, fcvtms and fcvtps. RISC-V fcvt.l.d carries the rule in a field of the instruction.

    From the lesson Scalar floating-point arithmetic

  276. What does a double-to-integer conversion return when the value does not fit?

    AArch64 and RISC-V saturate to the largest or smallest integer (AArch64 gives 0 for a NaN, RISC-V the largest integer). x86-64 returns its integer-indefinite value, the smallest integer, instead.

    From the lesson Scalar floating-point arithmetic

  277. After fcmp d0, d1 with a NaN operand, why is b.lt not a safe less-than, and what is?

    An unordered compare leaves NZCV = 0011, and lt means N differs from V, which is true, so b.lt branches as if NaN were smaller. Use mi (N set) for an ordered less-than, or test vs first and treat unordered as its own case.

    From the lesson Scalar floating-point arithmetic

  278. What does a SIMD instruction do with a 128-bit register?

    It slices the register into equal lanes of the element size it names, 16 bytes, 8 halfwords, 4 int32 or 2 int64, and runs the same operation on every lane at once. Scalar code is one lane of the same register.

    From the lesson SIMD concepts and CPU feature detection

  279. Which SIMD families do the three ISAs have, and which are scalable?

    x86-64 has SSE, AVX and AVX-512 at fixed widths of 128, 256 and 512 bits. AArch64 has NEON fixed at 128 bits plus the scalable SVE and SME. RISC-V V 1.0 is scalable, with VLEN chosen by the hardware.

    From the lesson SIMD concepts and CPU feature detection

  280. How does a program ask each platform whether a vector feature is there?

    sysctlbyname on macOS, getauxval on Linux AArch64, riscv_hwprobe on Linux RISC-V and CPUID on x86-64, all before the first vector instruction runs. A missing answer counts as absent.

    From the lesson SIMD concepts and CPU feature detection

  281. What does macOS sysctlbyname report for a feature name it does not know, and how should detection read it?

    An error instead of a value. Treat the error exactly like 0, as absent, and take the fallback path; treating it as present can lead to SIGILL.

    From the lesson SIMD concepts and CPU feature detection

  282. What is run-time dispatch?

    Picking one implementation of a function once at start-up from what the hardware and the operating system report, and keeping the choice in a function pointer that every later call uses.

    From the lesson SIMD concepts and CPU feature detection

  283. How many vector iterations does a 128-bit NEON int32 add loop need for n elements?

    n / 4 rounded down, since each add of four 32-bit lanes handles four elements at once.

    From the lesson Your first SIMD loop

  284. Why does a NEON int32 loop need a scalar tail when n is not a multiple of 4?

    Each vector pass adds 4 elements, so the vector body stops with n % 4 of them left over. The scalar tail then runs n % 4 times to finish them; without it they would never be written.

    From the lesson Your first SIMD loop

  285. Which loop counts must a vector add function test before running its loops?

    Both of them: the number of full vector iterations and the tail count. Either can be zero, and a loop whose count is zero must be skipped.

    From the lesson Your first SIMD loop

  286. What does the four-lane int32 loop look like on x86-64 SSE2?

    The same shape as NEON: movdqu loads four int32 values, paddd adds the four lanes, and a scalar loop handles the leftover elements.

    From the lesson Your first SIMD loop

  287. Why does the RISC-V V version of the loop need no separate tail?

    vsetvli picks the number of lanes for each iteration, so the last iteration simply gets fewer and the vector loop handles the leftover itself.

    From the lesson Your first SIMD loop

  288. What makes a loop vector-length-agnostic?

    Every iteration asks the hardware how many elements it may handle, handles them, and moves the index on by that number until nothing is left. One binary then uses the full width of any core.

    From the lesson Vector-length-agnostic code: RVV, SVE and SME

  289. How does SVE handle the last, partial vector of a loop?

    whilelo builds a predicate marking only the lanes whose index is still below n. Loads give 0 in the other lanes and stores skip them, so no scalar tail is needed.

    From the lesson Vector-length-agnostic code: RVV, SVE and SME

  290. What does RVV's vsetvli return, and what happens on the last iteration?

    vl, the number of elements this iteration will handle. Once the remaining count fits in one vector, vl equals it, so the last iteration handles exactly what remains.

    From the lesson Vector-length-agnostic code: RVV, SVE and SME

  291. A vector-length-agnostic loop adds 40 elements on a core with 16 lanes. How do the iterations split?

    16, 16 and 8 lanes, three iterations and no second loop for the tail.

    From the lesson Vector-length-agnostic code: RVV, SVE and SME

  292. What does a predicate register do?

    It marks which vector lanes an instruction acts on. SVE has p0 to p15, with one bit per byte of a vector register; AVX-512 has the mask registers k0 to k7, one bit per lane.

    From the lesson Vector-length-agnostic code: RVV, SVE and SME

  293. What is the difference between a lane-wise SIMD instruction and a reduction?

    A lane-wise instruction such as add or smax works inside each lane and keeps each result in its lane. A reduction such as addv or smaxv combines every lane of a register into one value.

    From the lesson Reductions, shuffles and masks

  294. Why does a SIMD loop keep one partial result per lane and reduce only after the loop?

    No lane needs another lane's value until the end, so lane-wise instructions update all partial results each iteration. One smaxv after the loop matched the C result for 1,000 int32 values, instead of 250 across-lane reductions inside it.

    From the lesson Reductions, shuffles and masks

  295. How should the lanes of a vector maximum be initialised?

    With an element of the array, never with 0: an array of all negative values would otherwise report 0 as its maximum.

    From the lesson Reductions, shuffles and masks

  296. What do shuffles and table lookups do?

    They move or fetch lanes by position. A shuffle copies lanes to new places, by a fixed pattern (zip1, uzp1, ext, pshufd) or by a register of indexes; a table lookup (tbl, pshufb, vrgather) replaces each index with the table entry it names.

    From the lesson Reductions, shuffles and masks

  297. What is a comparison mask, and what is movemask for?

    A vector compare produces a mask that marks the lanes passing its test. A movemask step (pmovmskb on x86-64, a narrowing shift such as shrn on NEON) turns the marked lanes into bits of a general register that ordinary code can test or count.

    From the lesson Reductions, shuffles and masks

  298. Which commands show the assembly a compiler wrote and the machine code it produced?

    clang -S -o - prints the assembly to the terminal; clang -c followed by llvm-objdump -d decodes the object's machine code. --target= picks x86-64, AArch64 or RISC-V Linux, and Compiler Explorer does the same in a browser.

    From the lesson Reading compiler output: -O0 versus -O2

  299. Which lines of a compiler listing are instructions, and how do you hide the rest?

    Only the indented lines that begin with a letter. Compile with -fno-asynchronous-unwind-tables and filter with one grep -v to drop unwind notes, directives and comments; keep the labels, because branches go to them.

    From the lesson Reading compiler output: -O0 versus -O2

  300. Why is -O0 code so full of loads and stores?

    Each statement is compiled on its own, and each variable has a home in the frame that every statement reloads and writes back. The lesson's sum loop ran 15 instructions per element at -O0 and 4 at -O2.

    From the lesson Reading compiler output: -O0 versus -O2

  301. The C names are gone from an -O2 listing. How do you map it back to the source?

    By role: the arguments arrive in the registers the ABI fixes, the loop is the block with a branch back to its own label, and the register written last before ret holds the result.

    From the lesson Reading compiler output: -O0 versus -O2

  302. Why do engineers check -O2 output rather than -O0 output?

    The shipped build is optimised, and questions about speed, about a surviving bounds check or about what a flag added can only be answered from that build. -O0 still helps to learn how C maps to instructions and suits a debugger best.

    From the lesson Reading compiler output: -O0 versus -O2

  303. Which instructions do hardening flags add that no line of C asked for?

    -fcf-protection=full puts an endbr64 at each function's entry, and -fstack-protector-strong copies a canary into the frame on entry and calls __stack_chk_fail before returning if the copy has changed.

    From the lesson Reading compiler output: -O0 versus -O2

  304. How should you read idioms in an -O2 listing?

    As cheaper instructions that give the same result as the C. Read the listing idiom by idiom and map each one back to the operation it stands for.

    From the lesson Compiler idioms you will meet

  305. Why does a compiler write xor eax, eax rather than mov eax, 0?

    It takes 2 bytes instead of 5, and both clear the whole of rax. The one difference is the flags: xor sets them from its zero result, so a compiler uses mov when flags from an earlier compare must survive.

    From the lesson Compiler idioms you will meet

  306. What is strength reduction?

    Choosing a cheaper operation that gives the same result for every input, such as x * 5 as a shift and an add, or a division by a constant as a multiplication and a shift.

    From the lesson Compiler idioms you will meet

  307. Which idioms have a recognisable shape on all three ISAs?

    Zeroing a register, multiplying by small constants with lea or shifted adds, dividing by a constant with a multiply and a shift, bit tests, selects, rotated loops and sign extension.

    From the lesson Compiler idioms you will meet

  308. What four fingerprints of optimisation can you spot in a listing?

    A call that vanished is inlining; a final call turned into a branch is a tail call; a loop body written out several times is unrolling; vector registers working on a loop written for one element at a time are vectorisation.

    From the lesson Optimisation and undefined behaviour in assembly

  309. Why can -O2 compile the test x + n < x into a check of the sign of n?

    Signed overflow is undefined behaviour, so the compiler may assume it never happens. Without overflow, x + n < x holds exactly when n is negative, and the listing tests only that.

    From the lesson Optimisation and undefined behaviour in assembly

  310. What does -O2 turn on that -O0 leaves off?

    Values kept in registers, inlining of small functions, tail calls as jumps, constant folding, and unrolling and vectorising of loops when a cost model says it pays. It also relies on the language's rules, such as no signed overflow, so checks that depend on breaking them can vanish.

    From the lesson Optimisation and undefined behaviour in assembly

  311. What does inlining do to a call?

    It replaces the call with a copy of the called function's body, so the call, the return and the argument passing disappear and the copy is optimised together with its caller.

    From the lesson Optimisation and undefined behaviour in assembly

  312. What is loop unrolling, and what happens when the trip count is known while compiling?

    Writing the body out several times so each iteration does the work of several. With a known trip count the loop can be unrolled completely, leaving no counter and no branch back.

    From the lesson Optimisation and undefined behaviour in assembly

  313. Why does a C++ compiler mangle names?

    The linker matches symbols by name alone, and C++ lets many functions share one: overloads, methods of different classes, functions in different namespaces. Mangling encodes the scope and parameter types, so total_area(const Shape *) becomes _Z10total_areaPK5Shape.

    From the lesson C++ and Rust in assembly: names, vtables, checks

  314. Which rules govern C++ names on Linux and macOS, and how do you decode a mangled name?

    The Itanium C++ ABI's rules, so objects from different compilers link together; Rust's v0 names begin with _R. llvm-cxxfilt decodes both, with -_ for the extra underscore Mach-O adds.

    From the lesson C++ and Rust in assembly: names, vtables, checks

  315. What does a C++ virtual call look like in assembly?

    Two loads and an indirect jump or call: load the vtable pointer from the start of the object, load the method's address from a fixed slot of that table, then branch through the register.

    From the lesson C++ and Rust in assembly: names, vtables, checks

  316. What does Rust's v[i] compile to?

    An unsigned compare of i against the length, a branch to a panic path placed after the ret, and a single load on the common path.

    From the lesson C++ and Rust in assembly: names, vtables, checks

  317. When can a compiler turn a virtual call into a direct call?

    When it can prove which method the call reaches; it then replaces the table lookup with a direct call to that method, which is called devirtualisation.

    From the lesson C++ and Rust in assembly: names, vtables, checks

  318. What does -g add to an object file, and which tools read it?

    DWARF debug information in .debug_* sections, chiefly the line table that maps each code offset to a source line. llvm-objdump -S and llvm-addr2line read it to put an address back on its line of C.

    From the lesson Debug info: symbols and source mapping

  319. Does adding -g change the machine code or slow the program down?

    No. The .text of a -g build and of the same build without it were byte for byte equal; only the file grows. -O sets the optimisation independently, so -O2 -g gives a fast build that can still be debugged.

    From the lesson Debug info: symbols and source mapping

  320. What does strip --strip-debug remove, and what still works afterwards?

    It removes the debug sections but keeps the code and the symbol table, so the binary still runs and still shows function names, but an address can no longer be mapped to a source line.

    From the lesson Debug info: symbols and source mapping

  321. How can a release stay small and still have its crashes symbolised?

    Keep the debug information in a separate file, a .dSYM bundle on macOS or a .debug file on Linux, and symbolise crash addresses against it while shipping the small binary.

    From the lesson Debug info: symbols and source mapping

  322. When a debugger stops at a breakpoint, has the instruction at that address run yet?

    No. A breakpoint stops the program just before the instruction at its address runs, so the arrowed instruction of the stop report is still to come and every register shows the moment before it.

    From the lesson GDB and LLDB for assembly

  323. Why do addresses inside LLDB, or GDB on GNU/Linux, match the addresses in the file?

    Both launch programs with address randomisation switched off, so code and data load where the file says.

    From the lesson GDB and LLDB for assembly

  324. How does single-stepping find the instruction that went wrong?

    Run exactly one instruction (thread step-inst, GDB's stepi, si), then read the registers it names. The first value that differs from your expectation points at the faulty instruction, as w3 = 20 exposed a pre-index load.

    From the lesson GDB and LLDB for assembly

  325. What is the difference between stepi and nexti when the next instruction is a call?

    stepi follows the call and stops at the called function's first instruction. nexti runs the whole function as one step and stops after the call, with the result already in x0 or rax. LLDB names them thread step-inst and thread step-inst-over.

    From the lesson GDB and LLDB for assembly

  326. How can you try a fix in a running program before changing the source?

    Write the value you think is right into the register, register write w0 42 in LLDB or set $x0 = 42 in GDB, then continue: the program exited with status 42, which put the bug inside the function and nowhere after it.

    From the lesson GDB and LLDB for assembly

  327. How does a watchpoint differ from a breakpoint, and how many can you have?

    A watchpoint stops the program when a memory location is written, or read, whichever instruction does it; a breakpoint stops at an address of code. Debuggers use the processor's debug registers for watchpoints, so only a few can be active.

    From the lesson GDB and LLDB for assembly

  328. How do you make a debugging session repeatable on GDB and LLDB?

    Keep a map of the commands the two give different names, and put the session in a batch file: gdb -batch -x file or lldb --batch --source file runs it with no prompt.

    From the lesson GDB and LLDB for assembly

  329. What is a gdbstub, and why does debugging a program under QEMU need one?

    A small server next to the program that does only the work that touches the machine: registers, memory, breakpoints, steps and stop reports. Under QEMU the instructions run inside the emulator, out of reach of your OS's debugging interface, so QEMU's own stub serves the debugger.

    From the lesson Debugging other ISAs with QEMU's gdbstub

  330. When a debugger drives a gdbstub, what does each side do?

    The stub touches the machine; the debugger on your computer keeps the symbols, the disassembler and the commands. They share no memory, so the packet log shows everything the debugger asked and was told.

    From the lesson Debugging other ISAs with QEMU's gdbstub

  331. What does a GDB Remote Serial Protocol packet look like?

    A dollar sign, the data, a hash sign and a two-digit checksum: the sum of the data's characters modulo 256, in hexadecimal.

    From the lesson Debugging other ISAs with QEMU's gdbstub

  332. What do the packets Z0, c, vCont;s, T05 and W2a mean?

    Z0 asks for a software breakpoint, c continues and vCont;s steps; a T05 stop reply reports a stop by signal 5 and W2a reports that the program exited with status 0x2a, which is 42.

    From the lesson Debugging other ISAs with QEMU's gdbstub

  333. How does QEMU hold a program for a debugger, as one Linux program and as a whole board?

    qemu-user with -g and a port, such as qemu-riscv64 -g 1234, holds one program at its first instruction. qemu-system with -s -S holds a whole board before its first instruction. Neither stub asks for authentication.

    From the lesson Debugging other ISAs with QEMU's gdbstub

  334. Where can an emulator disagree with real hardware?

    On misaligned accesses, performance counters, caches, timing and memory ordering, so a result that depends on them must be checked on a board.

    From the lesson Debugging other ISAs with QEMU's gdbstub

  335. A program ended with exit status 139. What ended it?

    Almost certainly a signal: a shell reports 128 plus the signal number, and 139 is 128 + 11, SIGSEGV. In the same way 132 is SIGILL, 136 is SIGFPE, and 138 is SIGBUS on macOS.

    From the lesson Diagnosing crashes: SIGSEGV, SIGBUS, SIGILL, SIGFPE

  336. What kind of fault does each of SIGSEGV, SIGBUS, SIGILL and SIGFPE name?

    SIGSEGV an address with no page or no permission, SIGBUS another bad access, SIGILL an instruction the processor cannot run, and SIGFPE an arithmetic fault such as the x86 divide error.

    From the lesson Diagnosing crashes: SIGSEGV, SIGBUS, SIGILL, SIGFPE

  337. Do Linux and macOS give the same number and the same signal for every fault?

    No. SIGBUS is 7 on Linux (status 135) but 10 on macOS (status 138), and Linux sends SIGSEGV, not SIGBUS, for a write to a read-only page.

    From the lesson Diagnosing crashes: SIGSEGV, SIGBUS, SIGILL, SIGFPE

  338. Where does a debugger stop when a program crashes, and what happens if you resume?

    On the faulting instruction itself, with the address it used in plain view. Resuming only runs the same instruction again and faults again.

    From the lesson Diagnosing crashes: SIGSEGV, SIGBUS, SIGILL, SIGFPE

  339. Beyond the signal, what should you look for first after a crash?

    The faulting instruction and the address it used, from a debugger or a core file. The signal says what kind of fault it was, not where it happened.

    From the lesson Diagnosing crashes: SIGSEGV, SIGBUS, SIGILL, SIGFPE

  340. Two threads each run counter++ a million times on a shared variable. Why can the total come out below 2,000,000?

    The increment is three steps: load, add, store. When the other thread stores between one thread's load and its store, that store is overwritten and an increment is lost. How many are lost depends on timing, and in C the race is undefined behaviour.

    From the lesson Atomic instructions: lock, LL/SC and AMOs

  341. What makes an update atomic?

    Its read and its write are one indivisible step for every core, so no other write to the same memory can land between them and no update is lost or half applied.

    From the lesson Atomic instructions: lock, LL/SC and AMOs

  342. How does x86-64 make a read-modify-write instruction atomic?

    With the lock prefix, the byte f0, on one of a fixed list of read-modify-write instructions with a memory destination, such as lock xadd or lock cmpxchg. xchg with a memory operand is locked even without the prefix.

    From the lesson Atomic instructions: lock, LL/SC and AMOs

  343. How does an AArch64 exclusive pair update memory atomically?

    ldxr (or ldaxr) reads the value and marks the address; stxr (or stlxr) stores only if nothing wrote the address since, and reports 0 for success or 1 for failure. The code retries the pair until the store-exclusive reports 0.

    From the lesson Atomic instructions: lock, LL/SC and AMOs

  344. What do the Armv8.1 LSE atomics add?

    Single instructions that do the whole update in one step and return the old value: ldadd for an add, swp for a swap and cas for compare-and-swap, with no retry loop.

    From the lesson Atomic instructions: lock, LL/SC and AMOs

  345. Which atomic instructions does RISC-V offer?

    The A extension has AMOs such as amoadd and amoswap for simple updates and the lr and sc pair for everything else; Zacas adds amocas. The aq and rl bits set each instruction's ordering.

    From the lesson Atomic instructions: lock, LL/SC and AMOs

  346. Which atomic instruction does a compiler emit for AArch64?

    It depends on the target: Apple's arm64 gets LSE instructions such as ldaddal, base Armv8.0 Linux gets an exclusive loop, and outline atomics call a small helper that picks LSE or the loop when the program runs.

    From the lesson Atomic instructions: lock, LL/SC and AMOs

  347. How is a spinlock built from atomic instructions?

    A thread takes the lock word with an atomic swap, waits with plain loads while another thread holds it, and releases it with a store-release of 0. Updates made inside the lock are then safe even as plain loads and stores.

    From the lesson Atomic instructions: lock, LL/SC and AMOs

  348. Does a thread always see its own memory accesses in program order?

    Yes. Other cores may see them in a different order, and the memory model of the ISA says which orders they can observe.

    From the lesson Memory ordering: TSO, weak models and fences

  349. Two threads each store 1 to their own word and then load the other's word. How can both loads read 0?

    Each store may sit in its core's queue of pending stores while the load after it reads memory, so both loads finish before either store is visible to the other core. Runs of this test on Apple silicon showed it.

    From the lesson Memory ordering: TSO, weak models and fences

  350. What does x86-64's total store order allow that sequential consistency does not?

    Only one reordering: a load may complete before an earlier store to a different address. Stores still become visible in program order, and a core always sees its own stores.

    From the lesson Memory ordering: TSO, weak models and fences

  351. How much reordering do AArch64 and RISC-V allow?

    They are weakly ordered: all four pairs of accesses to different addresses (load-load, load-store, store-load, store-store) may be observed out of order unless a dependency, a fence or an acquire or release orders them.

    From the lesson Memory ordering: TSO, weak models and fences

  352. Which instructions restore every order between earlier and later accesses?

    A full fence: mfence or a locked instruction on x86-64, dmb ish on AArch64, fence rw, rw on RISC-V.

    From the lesson Memory ordering: TSO, weak models and fences

  353. How do acquire and release differ from a full fence?

    Each restores one direction. An acquire load keeps later accesses after it (ldar on AArch64, the aq bit on RISC-V); a release store keeps earlier accesses before it (stlr, the rl bit).

    From the lesson Memory ordering: TSO, weak models and fences

  354. What is a litmus test?

    A tiny multi-threaded program, run many times, whose outcomes show which reorderings a memory model allows. Store buffering and message passing are two classic ones.

    From the lesson Memory ordering: TSO, weak models and fences

  355. In what unit does a cache move memory, and how big is it?

    In whole aligned lines. Many x86-64 processors use 64 bytes; on the Apple silicon Mac used for the runs, sysctl reported 128. A miss brings in the whole line.

    From the lesson Caches, cache lines and false sharing

  356. Why does summing a large C matrix row by row run faster than column by column?

    C stores rows next to each other, so a row walk uses every element of each line it fetches: one miss serves 32 loads of 4-byte ints on a 128-byte line. A column walk jumps a whole row per load and needs a new line for every element.

    From the lesson Caches, cache lines and false sharing

  357. Both walks of the matrix do the same additions and give the same sum. What sets their cost?

    The order of the loads: whether consecutive loads fall in the same cache line or each needs a line of its own.

    From the lesson Caches, cache lines and false sharing

  358. What is spatial locality, and why does it matter for caches?

    The habit of using memory near memory just used, such as the next element of a row. It lets one fetched cache line serve many loads.

    From the lesson Caches, cache lines and false sharing

  359. What is false sharing?

    A slowdown in which threads write different variables that happen to sit in the same cache line, so the line keeps moving between their cores although no variable is actually shared.

    From the lesson Caches, cache lines and false sharing

  360. How fast can a dependency chain run, however many execution units the core has?

    One operation per operation latency. Each step waits for the result of the step before, so spare execution units sit idle.

    From the lesson Pipelines, latency and dependency chains

  361. What is the difference between latency and throughput?

    Latency is the wait for one result; throughput is how many results of a kind the core can finish each cycle. A chain pays the latency, independent work approaches the throughput, and latency times throughput bounds what parallelism can win.

    From the lesson Pipelines, latency and dependency chains

  362. How did several independent accumulators change the speed of the addition loop?

    With sixteen independent partial sums the loop ran about eight and a half times faster, because the out-of-order core could overlap the additions until every adder was busy. Fewer accumulators gave less.

    From the lesson Pipelines, latency and dependency chains

  363. A loop that sums an array into one double is latency-bound. What does that mean, and what do you try first?

    Each s += a[i] waits for the previous s, so the loop runs at the speed of one dependent addition. Try several independent accumulators combined at the end, then measure, and accept that the total may change in its last digits.

    From the lesson Pipelines, latency and dependency chains

  364. Why does a compiler split an integer sum into several accumulators by itself but not a floating-point sum?

    Integer addition is associative, so regrouping cannot change the result. Regrouping floating-point additions changes the rounding, so the compiler does it only with -ffast-math, which can change the last digits of the total.

    From the lesson Pipelines, latency and dependency chains

  365. What does llvm-mca tell you, and what does it leave out?

    It estimates the cycles of a block of instructions on a named core, deterministically and without running it. It models only the core's scheduler and assumes every access hits the cache, so real data must still be measured.

    From the lesson Pipelines, latency and dependency chains

  366. What does a correct branch prediction cost, and what does a wrong one cost?

    A correct guess costs almost nothing. A wrong one throws away the work done on the guessed path and refetches from the right target, ten or more cycles on current cores each time.

    From the lesson Branch prediction and speculative execution

  367. Why did sorting the input make the same branchy filter loop about ten times faster?

    The predictor learns from history. On shuffled data the branch's outcome was unpredictable and mispredicted again and again; sorted, the outcome came in long runs the predictor guesses right.

    From the lesson Branch prediction and speculative execution

  368. A filter loop is slow and someone blames the cache. How do you check whether branch misprediction is the cause?

    Sort the input and measure again: the loop reads the same memory in the same order, so a large drop means misprediction. A branchless version whose time no longer depends on the order confirms it, and perf stat on Linux reads the branch-miss counter directly.

    From the lesson Branch prediction and speculative execution

  369. The core discards the work it did on a wrong guess. What can a speculative load still leave behind?

    A trace in the cache. The core runs ahead on every guess, and what a speculative load brought into the cache can outlast the discarded work.

    From the lesson Branch prediction and speculative execution

  370. What makes a benchmark result trustworthy?

    Fixed inputs, warm-up runs, the best and the median of many timed runs, and a report that names the hardware and the counter used.

    From the lesson Measuring cycles: counters, perf and llvm-mca

  371. You time a function with rdtsc and divide the ticks by the advertised clock speed. What is wrong?

    The time-stamp counter on current x86-64 ticks at a fixed reference rate, not at the core's clock, and the core clock itself changes with the power state and turbo. Ticks divided by the counter's own rate give elapsed time; core cycles need the core's cycle counter or a tool such as perf.

    From the lesson Measuring cycles: counters, perf and llvm-mca

  372. Does a counter's stated frequency tell you how often its value changes?

    No. The frequency is the counter's unit, not how often its value really steps, so check the real step before trusting a short timing.

    From the lesson Measuring cycles: counters, perf and llvm-mca

  373. Why can a timed call whose result nobody uses report almost no time?

    The compiler may delete a call whose result is never used, so the timer brackets nothing. Use the result, or check the compiled code, before trusting the number.

    From the lesson Measuring cycles: counters, perf and llvm-mca

  374. Where can a reported cycle count come from?

    From a cycle counter that the kernel guards, from perf on Linux, or from a model such as llvm-mca. A reported number should say which of them it came from.

    From the lesson Measuring cycles: counters, perf and llvm-mca

  375. How does a stack protector notice an overflow, and when?

    The function copies a guard word into its frame on entry, from __stack_chk_guard (or %fs:40 on x86-64), and compares it just before returning. An overflow that ran over the copy changes it, and the failed compare ends the process at the return.

    From the lesson Hardening in compiler output: canaries, PIE, NX

  376. The stack protector is on. Is the program safe from buffer overflows?

    No. It detects one consequence afterwards: every byte was still written, and the recorded guarded build wrote 36 bytes into a 16-byte buffer before the check fired. Overflows away from the guard word go unnoticed; only the length comparison in the code removes the bug.

    From the lesson Hardening in compiler output: canaries, PIE, NX

  377. When does a library copy become a checked call, and where does it stop the program?

    When the compiler can see the size of the destination, as for a local array, it substitutes the checked form, which ends the process at the copy rather than at the return. A hand-written copy loop never becomes one.

    From the lesson Hardening in compiler output: canaries, PIE, NX

  378. Which macro switches the checked library calls on, and what does each platform do by default?

    _FORTIFY_SOURCE. Apple's headers already give it the value 2; with the GNU C library you define it yourself, and the OpenSSF guide recommends -D_FORTIFY_SOURCE=3 with -O2 or higher.

    From the lesson Hardening in compiler output: canaries, PIE, NX

  379. Which defences are fields in the file rather than instructions?

    PIE, the stack's permissions (non-executable on ELF through the GNU_STACK header) and the read-only relocation window. The linker writes them, so you read them from the headers of a file you never run.

    From the lesson Hardening in compiler output: canaries, PIE, NX

  380. A hardening report shows every defence switched on. What does it prove?

    Only that the defences are on. None of the fields says the program has no memory bugs; each mitigation changes what a remaining bug can do, it does not remove it.

    From the lesson Hardening in compiler output: canaries, PIE, NX

  381. Which instruction marks a permitted indirect-branch target on each ISA, and what happens elsewhere?

    endbr64 on x86-64, bti c on AArch64 and lpad on RISC-V. With the check switched on, an indirect branch to any other instruction faults.

    From the lesson Hardware control-flow integrity: CET, PAC, BTI

  382. What are the two ways hardware protects a return address?

    Arm signs the saved address in place, with pacibsp on entry and retab to check it on return. A shadow stack keeps a second copy of each return address, and the processor compares it with the normal one at each return.

    From the lesson Hardware control-flow integrity: CET, PAC, BTI

  383. A disassembly shows pacibsp and retab. Are the returns protected?

    Not necessarily. On a Mac only the arm64e slice enforces them; in a plain arm64 build they run as no-ops, and a corrupted return still jumps. Build, slice and run-time state are three separate facts to check.

    From the lesson Hardware control-flow integrity: CET, PAC, BTI

  384. How can you tell what a binary was built for without running it?

    Read its property note, the .note.gnu.property section in which the compiler records features such as landing pads or a shadow stack, for the loader to read.

    From the lesson Hardware control-flow integrity: CET, PAC, BTI

  385. How can a secret leak through timing even when every result is correct?

    When a secret value decides a branch or a memory address, the time the code takes, or what it leaves in the cache, depends on that value.

    From the lesson Constant-time code and data-independent timing

  386. Why is memcmp the wrong way to check a received authentication tag?

    It may stop at the first byte that differs, so its time tells how many leading bytes were right. Use a routine documented as constant time, such as CRYPTO_memcmp or sodium_memcmp, or a loop that reads every byte.

    From the lesson Constant-time code and data-independent timing

  387. What does a constant-time comparison of two buffers do?

    It reads every byte, folds the differences together with XOR and OR, and tests the total once at the end, so it runs the same instructions for any contents of the same length.

    From the lesson Constant-time code and data-independent timing

  388. How do you check that a comparison really is constant time?

    From the compiled code: count the instructions it runs on several inputs of the same length and treat any difference as a failure, because the compiler decides which branches exist.

    From the lesson Constant-time code and data-independent timing

  389. What does DIT on Apple silicon fix, and what does it leave alone?

    It makes a listed set of instructions take the same time whatever values they process. It does not change which branches a program takes or which addresses it reads.

    From the lesson Constant-time code and data-independent timing

  390. What is reverse engineering a binary you built, and where does it start?

    Reading the compiled file to recover what it does when the source is not at hand. It starts with whether you are allowed to read it: that depends on who owns the program and its licence.

    From the lesson Reverse engineering binaries you built

  391. In what order should you read an unfamiliar binary?

    From the outside in: identify the file and its processor, find its entry, then read its imports and strings, so you know the landmarks before you read any instructions.

    From the lesson Reverse engineering binaries you built

  392. Which commands give the cheapest clues about a stripped Mach-O or ELF file?

    file and otool -hv (readelf -h for ELF) give the format, the processor and whether the code is position-independent; otool -Iv lists the imported library functions; strings prints its readable text.

    From the lesson Reverse engineering binaries you built

  393. Why does a stripped file still name the library functions it calls?

    The loader binds imports by name, so those names must stay. Stripping hides the program's own names, not what it calls.

    From the lesson Reverse engineering binaries you built

  394. How do you confirm what the imports and strings suggest before reading code?

    Run the program on inputs you choose, such as an empty word and two words one letter apart, and compare the outputs.

    From the lesson Reverse engineering binaries you built

  395. What are the privilege levels called on x86-64, AArch64 and RISC-V?

    Rings 3 (applications) and 0 (the kernel) on x86-64; EL0 to EL3 on AArch64, from applications up through the kernel and hypervisor to the secure monitor; U, S and M modes on RISC-V, where M is the only mode every core must have.

    From the lesson Privilege levels, exceptions and interrupts

  396. Is the privilege level a property of the code?

    No, it is state inside the processor. Code moves up only through a trap, and comes back down through the return instruction that the more privileged code chooses to run.

    From the lesson Privilege levels, exceptions and interrupts

  397. What are the three ways into the kernel, and which are synchronous?

    A system call, a fault and an interrupt. The first two are synchronous exceptions caused by the running instruction; an interrupt is asynchronous, raised by a device such as the timer between two unrelated instructions.

    From the lesson Privilege levels, exceptions and interrupts

  398. What does the processor save when it takes a trap?

    The return point (ELR_EL1, mepc, or RCX after syscall), the old state (SPSR_EL1, mstatus, or R11) and the cause. It then raises the privilege level and jumps to the handler the kernel installed; eret, mret or sysretq undo the entry.

    From the lesson Privilege levels, exceptions and interrupts

  399. Where does the program resume after a fault, after a system call and after an interrupt?

    A fault returns to the failing instruction so it can run again once the kernel has fixed the cause. A system call resumes after the call; RISC-V ecall saves its own address, so the handler adds 4 to mepc before mret. An interrupt resumes at the instruction that had not run yet.

    From the lesson Privilege levels, exceptions and interrupts

  400. How does a trap in user code reach the program?

    As a signal such as SIGILL, SIGSEGV or SIGTRAP. On macOS the handler's context also carries the syndrome and the fault address.

    From the lesson Privilege levels, exceptions and interrupts

  401. How is the cause of an exception recorded on each ISA?

    AArch64's ESR_EL1 splits into the class EC, the length bit IL and the details ISS. RISC-V's mcause sets its top bit for an interrupt and holds a code; x86-64 identifies the event by its vector number.

    From the lesson Privilege levels, exceptions and interrupts

  402. What must a bare-metal program set up for itself?

    Everything an operating system would: its linker script fixes where it loads, and its first instructions set up a stack before anything else runs. It is the only software on the machine.

    From the lesson Bare metal: hello from the UART on QEMU

  403. How does a bare-metal program on QEMU's RISC-V virt board print a character?

    By memory-mapped I/O: it stores the byte to the UART's transmit register, the first byte of the NS16550A at 0x10000000. The store itself is the command, with no system call involved.

    From the lesson Bare metal: hello from the UART on QEMU

  404. How does a bare-metal program end a QEMU run on the RISC-V virt board?

    It stores 0x5555 to the test device at 0x100000.

    From the lesson Bare metal: hello from the UART on QEMU

  405. Where must a RISC-V bare-metal image for QEMU's virt board begin when QEMU runs with -bios none?

    At 0x80000000, the first byte of the board's RAM, because that is where execution starts; the image is linked for that address.

    From the lesson Bare metal: hello from the UART on QEMU

  406. How do you check an image's entry point and segments before running it?

    llvm-readelf -h -lW prints the entry point and the program headers, the segments a loader copies and where they go.

    From the lesson Bare metal: hello from the UART on QEMU

  407. How does an 8086 form a physical address from a segment and an offset?

    It shifts the 16-bit segment left by 4 bits, which multiplies it by 16, and adds the 16-bit offset, giving a 20-bit address. Segments start every 16 bytes and overlap, so 07C0:0000 and 0000:7C00 name the same byte, 0x07C00.

    From the lesson 16-bit x86: real mode and the 8086

  408. What happened to FFFF:FFFF on an 8086, and what do later processors do?

    It needs 21 bits, 0x10FFEF, so the 8086 wrapped it to 0x0FFEF. Later processors keep the carry unless the A20M# signal masks address line 20.

    From the lesson 16-bit x86: real mode and the 8086

  409. In which mode does every x86 processor leave reset, and where is its first instruction?

    In 16-bit real mode, fetching its first instruction at FFFFFFF0H, 16 bytes below 4 GiB, where the board maps its firmware.

    From the lesson 16-bit x86: real mode and the 8086

  410. What does a legacy BIOS do with the first sector of a disk?

    It loads those 512 bytes at physical address 0x7C00 and jumps to them in real mode, provided the sector ends with the signature bytes 55 AA at offsets 510 and 511.

    From the lesson 16-bit x86: real mode and the 8086

  411. How does UEFI differ from a legacy BIOS when it starts a loader on x64?

    UEFI switches the processor into 64-bit long mode itself and starts the operating-system loader as a 64-bit program, instead of running a 512-byte sector in real mode.

    From the lesson 16-bit x86: real mode and the 8086

  412. Which registers does an A32 instruction name, and which have fixed jobs?

    Sixteen: r0 to r12 for any use, r13 as the stack pointer by convention, r14 as the link register that bl writes, and r15 as the program counter, which instructions read and write like a register.

    From the lesson 32-bit Arm: registers, CPSR and processor modes

  413. What value does an A32 instruction get when it reads r15?

    Its own address plus 8, two instructions ahead, a trace of the original three-stage pipeline. PC-relative offsets in A32 listings count from there; in T32 code the read gives the address plus 4.

    From the lesson 32-bit Arm: registers, CPSR and processor modes

  414. Which fields does the CPSR hold, from the top bits down?

    The N, Z, C, V and Q flags at the top, the A, I and F masks in bits 8 to 6, the state bit T in bit 5 and the five mode bits in bits 4 to 0. A set mask bit means that kind of interrupt waits.

    From the lesson 32-bit Arm: registers, CPSR and processor modes

  415. What can an msr in User mode change in the CPSR?

    Only the flags, Q, GE and E. The interrupt masks and the mode bits stay exactly as they were, because only a privileged mode, which every mode but User is, may change them.

    From the lesson 32-bit Arm: registers, CPSR and processor modes

  416. In which mode does a classic Arm core normally start after reset?

    In Supervisor mode, with the interrupts masked.

    From the lesson 32-bit Arm: registers, CPSR and processor modes

  417. What does a register name such as sp or r8 mean on a 32-bit Arm core?

    A physical register chosen together with the mode bits. Each exception mode has its own sp, lr and SPSR, FIQ also its own r8 to r12, and System mode shares every register with User mode.

    From the lesson 32-bit Arm: registers, CPSR and processor modes

  418. Why does FIQ mode have its own r8 to r12 when IRQ mode does not?

    FIQ is built for fast data transfers, where every register a handler saves and restores costs time. A handler that keeps to its own banked registers saves nothing at all.

    From the lesson 32-bit Arm: registers, CPSR and processor modes

  419. Why can Apple silicon not run A32 programs directly, and where does T32 code run?

    Apple silicon reports no AArch32 state, so the lesson's A32 programs ran on a model of the instruction set. Cortex-M microcontrollers run T32 code only.

    From the lesson 32-bit Arm: registers, CPSR and processor modes

  420. What does the barrel shifter do in an A32 data-processing instruction?

    It shifts or rotates the second operand on its way into the ALU, within the same instruction: add r1, r2, r3, lsl 3 adds r3 times 8 to r2, so one add can multiply by 9. The shifted register keeps its value.

    From the lesson A32 data processing and the barrel shifter

  421. What can Operand2 of an A32 data-processing instruction be?

    A rotated immediate, a register, or a register shifted by a constant, shifted by the bottom byte of another register, or rotated one place through C with rrx.

    From the lesson A32 data processing and the barrel shifter

  422. How does A32 fit a constant into 12 bits of an instruction?

    As an 8-bit value rotated right by twice a 4-bit field, so 0xFF000000 is 0xFF rotated right by 8. A value such as 257 has no such form.

    From the lesson A32 data processing and the barrel shifter

  423. What does the S suffix change in an A32 instruction, and what is a compare?

    Without S the instruction leaves the flags alone; with S it also sets N and Z from its result. A compare is an S instruction that keeps only the flags and discards the result.

    From the lesson A32 data processing and the barrel shifter

  424. What does rrx do?

    Rotates a register right by one place through the C flag: each bit moves one place right, the old C enters at bit 31, and the bit that leaves from bit 0 is the new carry candidate.

    From the lesson A32 data processing and the barrel shifter

  425. How can A32 code write a short if and else without a branch?

    Almost every A32 instruction carries a four-bit condition, so suffixes such as subgt and sublt run an instruction only when the flags match. An instruction whose condition fails is simply skipped.

    From the lesson A32 branches, load-store and stack instructions

  426. What does the ! in ldr r0, [r1, 4]! do, and how does ldr r0, [r1], 4 differ?

    The ! asks for writeback: the load reads at r1 + 4 and keeps that address in r1. The post-indexed form reads at the old r1 first and then adds 4, which is why loops over arrays use it. The plain offset form leaves r1 unchanged.

    From the lesson A32 branches, load-store and stack instructions

  427. In what order do ldm and stm move a list of registers?

    To or from consecutive words, with the lowest-numbered register at the lowest address, regardless of how the register list is written.

    From the lesson A32 branches, load-store and stack instructions

  428. What are push and pop on a 32-bit Arm core?

    Block transfers on a full descending stack: push assembles to stmdb sp! and pop to ldmia sp!. The stack grows down and sp holds the address of the last item stored.

    From the lesson A32 branches, load-store and stack instructions

  429. How do Thumb and Thumb-2 shrink code, and what did the lesson's summing function show?

    Thumb gave A32's commonest operations 2-byte encodings, and Thumb-2 added 4-byte ones, so one T32 function mixes both widths. The same seven-instruction function took 16 bytes in T32 against 28 in A32.

    From the lesson Thumb, Thumb-2 and the Cortex-M exception model

  430. How does a BX or BLX branch know whether its target is A32 or T32 code?

    From bit 0 of the target address: set means T32, clear means A32. The symbol of every T32 function already carries the bit.

    From the lesson Thumb, Thumb-2 and the Cortex-M exception model

  431. Which instruction set does a Cortex-M core run?

    T32 only. An M-profile core has no A32 state, and its exception model has a mode of its own for each job: Thread mode for program code and Handler mode for exception handlers.

    From the lesson Thumb, Thumb-2 and the Cortex-M exception model

  432. Why can a Cortex-M exception handler be an ordinary C function?

    The core itself pushes eight words of state on entry and leaves the special value EXC_RETURN in lr. The handler ends with bx lr, and loading EXC_RETURN into the pc ends the exception.

    From the lesson Thumb, Thumb-2 and the Cortex-M exception model

  433. Where does a classic Arm core go when it takes an exception?

    To one of eight vectors at address 0, or at 0xFFFF0000 when the high-vectors option is selected. Each vector belongs to one kind of exception with its own mode and masks.

    From the lesson Exceptions, IRQ and FIQ on classic Arm

  434. What does a classic Arm core save when it takes an IRQ?

    The return point in IRQ mode's own lr and the CPSR in IRQ mode's SPSR; then it switches to IRQ mode and sets the I bit, so a second IRQ waits.

    From the lesson Exceptions, IRQ and FIQ on classic Arm

  435. What do the I and F bits of the CPSR do, and who may change them?

    They hold IRQ and FIQ requests back: a request that arrives while its bit is set waits until the bit is cleared. Only privileged code can change them.

    From the lesson Exceptions, IRQ and FIQ on classic Arm

  436. How does taking an FIQ differ from taking an IRQ?

    FIQ mode has its own r8 to r14 and SPSR, so its handler can start without saving registers, and entry sets both the I and the F bits.

    From the lesson Exceptions, IRQ and FIQ on classic Arm

  437. What is a nested interrupt?

    An interrupt taken while the handler of an earlier one is still running, so the earlier handler pauses until the later one returns.

    From the lesson Exceptions, IRQ and FIQ on classic Arm

  438. What route did every 32-bit Arm lab program take from source to result?

    llvm-mc assembled it for ARMv5TE, ld.lld linked it, the A32 model ran it, and the end state, its registers, flags and memory, was read back.

    From the lesson 32-bit Arm lab programs and exam practice

  439. What counts as the evidence of a lab run?

    The registers and memory words the program promised, plus the flags of the instructions that set them, read from the state the run left behind.

    From the lesson 32-bit Arm lab programs and exam practice

  440. When does a lab result count as correct?

    When it equals a value worked out before the run, on inputs chosen so that a wrong program would fail.

    From the lesson 32-bit Arm lab programs and exam practice

  441. After muls computes 65,535 x 65,535, why does the N flag read 1?

    muls sets N from bit 31 of the 32-bit result, 0xFFFE0001, whose top bit is 1, although the true product is positive. Each flag records only what the instruction that set it measured.

    From the lesson 32-bit Arm lab programs and exam practice

  442. Which inputs make a wrong lab program fail?

    The smallest and largest values, repeated values, and negative numbers wherever the signed and unsigned answers differ.

    From the lesson 32-bit Arm lab programs and exam practice

  443. How does the AS assembler route turn 8051 or 8085 source into a file a loader takes?

    AS translates the Intel-syntax source into a code file and a listing that pairs every line with its address and bytes, and p2hex writes those bytes out as Intel HEX.

    From the lesson 8-bit toolchains: assemblers, HEX and simulators

  444. Why does AS refuse the constant FFH, and how is it written instead?

    A hexadecimal constant must begin with a digit, or it reads as a name, so FFH is refused and 0FFH is the accepted form.

    From the lesson 8-bit toolchains: assemblers, HEX and simulators

  445. What does an Intel HEX record hold, and how is its checksum checked?

    After the colon: a byte count, a 16-bit load address with its high byte first, a record type (00 data, 01 end of file), the data bytes and a checksum. Every byte of a correct record, checksum included, adds up to 00H modulo 256; a loader refuses any line whose total differs.

    From the lesson 8-bit toolchains: assemblers, HEX and simulators

  446. How was the checksum of the record :022050009A7F75 worked out?

    The bytes 02, 20, 50, 00, 9A and 7F add up to 18BH, whose low byte is 8BH, and 100H minus 8BH is 75H, the record's last byte.

    From the lesson 8-bit toolchains: assemblers, HEX and simulators

  447. What does uCsim show at a breakpoint, and how did its clock count for sum.asm compare with Intel's timing?

    Registers, flags, internal RAM and the clock count. For sum.asm its 1,512 clock periods matched Intel's timing: 126 machine cycles of 12 periods, 136.7 microseconds at 11.0592 MHz.

    From the lesson 8-bit toolchains: assemblers, HEX and simulators

  448. Why does uCsim start a program from random register and RAM values?

    Real chips hold unknown bytes at power-on, so the simulator starts from values chosen by a seed. A program that sets every register, pointer and byte it reads gives the same result for every seed; one that does not changes with the seed.

    From the lesson 8-bit toolchains: assemblers, HEX and simulators

  449. How do two assemblers and two simulators check each other?

    Compare the assemblers on the bytes they produce and the simulators on registers, memory and time for the same program. A disagreement exposes a fault in one of the tools and points at the line or starting state that differs.

    From the lesson 8-bit toolchains: assemblers, HEX and simulators

  450. Which tools fit which 8-bit lab?

    Sim8085 and GNUSim8085 run 8085 programs in a window, EdSim51 shows an 8051 on a virtual board, uCsim runs both kinds of chip from command files, and Keil's C51 evaluation tools stop at 2 KB of object code.

    From the lesson 8-bit toolchains: assemblers, HEX and simulators

  451. What makes the 8085 an accumulator machine, and how does that shape a calculation?

    Its 8-bit arithmetic, logic and rotates read A and leave the result in A, so a calculation is a chain of load A, operate, store A. Only increments, decrements and DAD do arithmetic in other registers; RISC-V's add a2, a0, a1 needs none of those moves.

    From the lesson The 8085 for assembly programmers: a bridge

  452. What are BC, DE, HL and M on the 8085?

    BC, DE and HL pair the 8-bit registers into 16-bit values and addresses, and M names the byte in memory at the address held in HL.

    From the lesson The 8085 for assembly programmers: a bridge

  453. Where do the 8085's five flags sit, and which instructions leave them alone?

    At bits 7, 6, 4, 2 and 0 of one byte, the same places x86's LAHF still puts them. An add writes all five, DAD writes only CY, and INX writes none.

    From the lesson The 8085 for assembly programmers: a bridge

  454. How can you tell an 8085 instruction's addressing mode from its bytes?

    Its length and bytes give it away: direct, register, register indirect or immediate. A direct form carries a 16-bit address, an immediate form carries the data itself, and a register form is a single byte.

    From the lesson The 8085 for assembly programmers: a bridge

  455. Which separate address spaces does the 8051 have, and how is each reached?

    Program memory, external data memory and internal data memory. Code is fetched, or read as data with MOVC; external data is reached with MOVX; internal data with ordinary moves. The same address names a different byte in each.

    From the lesson The 8051: memory map, registers and SFRs

  456. How does an 8051 program choose which bytes R0 to R7 are?

    Bits RS1 and RS0 of PSW, bits 4 and 3, select one of four banks of eight bytes from 00H-07H to 18H-1FH. A reset selects bank 0, and switching banks copies nothing.

    From the lesson The 8051: memory map, registers and SFRs

  457. How does a program switch register banks, and can it still read another bank?

    By writing PSW, as MOV PSW,#08H does, or a bit of it, as SETB RS0 does. The other banks keep their bytes, and a direct address such as 08H reads R0 of bank 1 from any bank.

    From the lesson The 8051: memory map, registers and SFRs

  458. What do internal bytes 20H to 7FH of the 8051 hold?

    Bytes 20H to 2FH carry bit addresses 00H to 7FH, so the number after a bit instruction is a bit address; 30H to 7FH is general RAM.

    From the lesson The 8051: memory map, registers and SFRs

  459. How does an 8051 instruction reach a special function register?

    Only through a direct address from 80H to FFH, such as the address of the accumulator, PSW, SP, the port latches or the timer controls.

    From the lesson The 8051: memory map, registers and SFRs

  460. Where can an 8051 operand be found?

    In the instruction (immediate), in R0 to R7 (register), at an address the instruction names (direct), at an address held in R0 or R1 (register indirect), or in code memory at a base plus A (indexed).

    From the lesson 8051 assembly: addressing modes and lab programs

  461. How does a program reach the upper 128 bytes of an 8052's internal RAM?

    Only indirectly, through R0 or R1, because direct addresses from 80H up name the special function registers.

    From the lesson 8051 assembly: addressing modes and lab programs

  462. How many times does a DJNZ loop run for a count of N, and for a count of 0?

    DJNZ subtracts 1 and then jumps unless the counter is 0, so a count of N gives N iterations, and a count of 0 gives 256, because the first decrement wraps 0 to 255.

    From the lesson 8051 assembly: addressing modes and lab programs

  463. What does CJNE do, and how does a loop use it?

    It compares two bytes and jumps while they differ, so a loop repeats until a byte reaches the value it is compared with.

    From the lesson 8051 assembly: addressing modes and lab programs

  464. How does the 8051 read a table kept in code memory?

    With indexed addressing: MOVC A,@A+DPTR or MOVC A,@A+PC reads the code byte at DPTR or the PC plus the value in A.

    From the lesson 8051 assembly: addressing modes and lab programs

  465. Which registers set a timer's mode and start it on the 8051?

    TMOD sets each timer's mode and TCON starts the count; a timer sets its flag, such as TF0, when its count rolls over.

    From the lesson 8051 timers, serial port and interrupts

  466. How does a polled 1 ms delay work at 11.0592 MHz?

    Timer 0 in mode 1 is loaded with FC66H and started, and the program waits in a loop until TF0 shows the overflow.

    From the lesson 8051 timers, serial port and interrupts

  467. What does a timer do in mode 2?

    It reloads the count from TH by itself at every overflow, so each period has the same length with no instruction spent on it.

    From the lesson 8051 timers, serial port and interrupts

  468. How does an 8051 clocked at 11.0592 MHz send a byte at 9,600 baud?

    Serial mode 1 sends 10 bits for each byte written to SBUF, with Timer 1 at TH1 = FDH setting 9,600 baud. TI says the byte is out, and the program must clear it before the next one.

    From the lesson 8051 timers, serial port and interrupts

  469. How does an 8051 interrupt reach its handler, and how does the handler end?

    Each source calls a fixed vector, from 0003H to 0023H; the call saves only the return address, and the handler ends with RETI.

    From the lesson 8051 timers, serial port and interrupts

  470. Into which families do most 8051 lab programs fall?

    Data transfer, arithmetic, logical and branch programs, each resting on a few instructions.

    From the lesson 8051 lab programs and viva practice

  471. What four parts does a lab record have?

    The aim, the algorithm as numbered steps, the listing with every address and byte, and the result read from the run.

    From the lesson 8051 lab programs and viva practice

  472. How is the 8051 block-move program shown to be right?

    By its memory dump: after the run 40H to 49H equal 30H to 39H, 4AH to 4FH are unchanged, and the copy took 63 machine cycles.

    From the lesson 8051 lab programs and viva practice

  473. What makes a viva answer strongest?

    Pointing at a register, a byte or a count that the run printed, rather than a general description.

    From the lesson 8051 lab programs and viva practice

  474. What is an x86-64 micro-architecture level, and what does building for x86-64-v3 promise?

    A named set of features defined by the psABI: v2, v3 and v4. Code built with -march=x86-64-v3 may use AVX, AVX2, BMI1, BMI2, FMA, F16C, LZCNT and MOVBE anywhere without a run-time test, so it is faster on a v3 processor and stops with SIGILL on an older one.

    From the lesson x86-64 today: levels, APX and AVX10

  475. How do Linux distributions ship code for newer x86-64 levels without dropping older processors?

    They keep the baseline build and add level-specific copies in glibc-hwcaps folders, such as glibc-hwcaps/x86-64-v3, which the dynamic linker picks only on a processor that reaches the level. Some ship a whole package variant, such as Ubuntu's amd64v3.

    From the lesson x86-64 today: levels, APX and AVX10

  476. What does Intel APX add to the general-purpose registers, and how are the new ones reached?

    Sixteen more registers, r16 to r31, reached through the two-byte REX2 prefix (0xD5 and a payload byte) or an extended EVEX prefix. Two correct assemblers may pick different bytes for the same APX instruction.

    From the lesson x86-64 today: levels, APX and AVX10

  477. What is APX's new data destination form?

    An instruction such as add writes its result to a third register and only reads its two sources, as in add r20, r21, r22, instead of overwriting one of them.

    From the lesson x86-64 today: levels, APX and AVX10

  478. How do Linux and Windows treat the APX registers r16 to r31 across calls?

    System V lets a function write all sixteen without saving them. Microsoft's ABI makes r30 and r31 callee-saved, so a Windows function saves and restores them like rbx.

    From the lesson x86-64 today: levels, APX and AVX10

  479. What does AVX10 change about how x86-64 vector features are named?

    It includes all of AVX-512 behind one CPUID feature flag and an inclusive version number, instead of one flag per instruction family, and new vector instructions now arrive only there.

    From the lesson x86-64 today: levels, APX and AVX10

  480. What does a FEAT_ name such as FEAT_LSE tell you, and what does it not?

    It names one architectural feature, and Arm's list gives the version that made it optional and often the one that made it mandatory: FEAT_LSE is optional from Armv8.0 and mandatory from Armv8.1. It does not say whether a given processor has it, or whether your binary uses it.

    From the lesson AArch64 today: Armv9 features on real machines

  481. What does targeting an Arm architecture version promise about features?

    Only the features that version makes mandatory. LSE is mandatory from Armv8.1 and BTI from Armv8.5, while MTE, SVE and SME stay optional even in Armv9.

    From the lesson AArch64 today: Armv9 features on real machines

  482. How does a program learn which Arm features a machine really has?

    It reads the operating system's report: sysctl keys on macOS, HWCAP bits on Linux, which a program reads with getauxval(AT_HWCAP), one bit per feature such as HWCAP_ATOMICS.

    From the lesson AArch64 today: Armv9 features on real machines

  483. Besides the machine's report, what must you check before relying on an Arm feature?

    The instructions your compiler actually emitted, since the feature name and the machine's support say nothing about whether the binary uses it.

    From the lesson AArch64 today: Armv9 features on real machines

  484. How is a RISC-V ISA string such as rv64gc_zba built?

    The base first, then single-letter extensions in canonical order, then multi-letter extensions after underscores, each with an optional version.

    From the lesson RISC-V today: extensions, profiles and RVA23

  485. What does rv64gc stand for, and what must a machine have to run code built for it?

    The 64-bit base RV64I plus G and C. G stands for M, A, F and D with Zicsr and Zifencei: multiply and divide, atomics, single and double floating point, the CSR instructions and the instruction-fetch fence. C adds the 16-bit compressed forms. The machine needs all of them, because the compiler may use any.

    From the lesson RISC-V today: extensions, profiles and RVA23

  486. What is a RISC-V profile such as RVA23U64, and why does it help?

    A ratified list of mandatory extensions for one class of processor and privilege mode, which software targets by one name instead of a long ISA string. LLVM's recorded arch string grows from 18 names for RVA20U64 to 51 for RVA23U64.

    From the lesson RISC-V today: extensions, profiles and RVA23

  487. What does RVA23U64 make mandatory that earlier profiles did not?

    The vector extension V, along with 11 other new extensions. Ubuntu 26.04 LTS supports only RVA23S64 hardware.

    From the lesson RISC-V today: extensions, profiles and RVA23

  488. Which processors does India's DIR-V programme build on?

    The SHAKTI processors of IIT Madras and the VEGA processors of C-DAC.

    From the lesson RISC-V today: extensions, profiles and RVA23

  489. How do x86-64, AArch64 and RISC-V differ in instruction length, memory access and flags?

    x86-64 has 1- to 15-byte instructions, memory operands and flags. AArch64 has 4-byte instructions, touches memory only with loads and stores, and sets flags on request. RISC-V has 4- or 2-byte instructions, loads and stores only, and no flags.

    From the lesson Comparing x86-64, AArch64 and RISC-V

  490. How does each ISA decide a conditional branch?

    x86-64 branches on RFLAGS, which most arithmetic sets (cmp then jl). AArch64 branches on NZCV, which only flag-setting forms change (cmp then b.lt), and cbz tests a register directly. RISC-V compares two registers inside the branch itself, as blt a0, a1, label does.

    From the lesson Comparing x86-64, AArch64 and RISC-V

  491. Five C functions were compiled at -Os for four targets. What did the sizes show?

    RV64GC needed 122 bytes, x86-64 127, AArch64 176 and RV64G 188: the compressed 2-byte forms mattered more than the CISC or RISC label.

    From the lesson Comparing x86-64, AArch64 and RISC-V

  492. Which changed code size more for the same functions, the ISA or the optimisation level?

    The optimisation level: x86-64 grew from 127 bytes at -Os to 271 at -O2, more than the gap between any two ISAs at -Os.

    From the lesson Comparing x86-64, AArch64 and RISC-V

  493. How should code size be compared across ISAs?

    With one source, one compiler and one set of options, because size comes from the operations each encoding makes short, such as memory operands on x86-64 and 2-byte forms on RISC-V.

    From the lesson Comparing x86-64, AArch64 and RISC-V

  494. How many operations can k opcode bits encode?

    2 to the power k. In a fixed format the operand fields take their bits first and the opcode gets the rest.

    From the lesson GATE COA: instructions and addressing modes

  495. How does an expanding opcode give formats with fewer operands more codes?

    Every code a format leaves unused becomes an escape: it opens 2 to the power n codes of the next format, where n is the number of operand bits that format gives up.

    From the lesson GATE COA: instructions and addressing modes

  496. How is an effective address formed, and which modes read no memory for the operand address?

    As a short sum of instruction fields, registers and the PC. Immediate and register modes read no memory to find the operand, and on the lesson's T16 machine PC-relative counts from the next instruction.

    From the lesson GATE COA: instructions and addressing modes

  497. How many memory reads does memory-indirect addressing need for one operand?

    Two: one for the pointer at the address in the instruction and one for the operand it points to. On T16, [[0x0400]] read the pointer 0x0300, then the operand 0x3333.

    From the lesson GATE COA: instructions and addressing modes

Back to the Assembly (x86-64, AArch64, RISC-V) track

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.