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)  Module 19 – The three ISAs today, compared

x86-64 today: levels, APX and AVX10

What x86-64-v2, v3 and v4 promise, how distributions ship them, how Intel APX encodes 32 registers and how AVX10 renumbers AVX-512, with recorded bytes.

  • Advanced
  • 20 minutes
  • Examples run with Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain what each x86-64 micro-architecture level promises and how a distribution ships a build for one
  • Decode an Intel APX instruction that names r16 to r31 or a new data destination
  • Compare how the System V and Microsoft ABIs treat the APX registers
  • Distinguish AVX-512 feature flags from an AVX10 version number

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain what each x86-64 micro-architecture level promises and how a distribution ships a build for one
  • Decode an Intel APX instruction that names r16 to r31 or a new data destination
  • Compare how the System V and Microsoft ABIs treat the APX registers
  • Distinguish AVX-512 feature flags from an AVX10 version number

Before you start

On this page

Compiled for the x86-64 baseline, a loop that adds two 16-element float arrays takes four 128-bit additions; compiled for x86-64-v3 it takes two, and the v3 build no longer runs on an older processor. Three things decide what current x86-64 code may assume: the micro-architecture levels v2 to v4, Intel’s APX, which takes the general-purpose registers from 16 to 32, and AVX10, which turns AVX-512’s many feature flags into one version number.

This page reads each of them in recorded compiler and assembler output from LLVM 23 and NASM 3.02. Every listing is compiled or assembled for x86-64 and read as the compiler’s assembly or as the encoded bytes; running APX or AVX10 code needs a processor that implements them, so the page shows what the listings say rather than what a run printed.

A level is a promise about a whole set of features

The x86-64 psABI groups processor features into named micro-architecture level: A named set of x86-64 processor features defined by the psABI: x86-64-v2, v3 and v4. Code built for a level may use all of its features, and those of the levels below it, without testing for them at run time.. A build for a level may use every feature of that level and of the levels below it, with no test at run time. The table is the psABI’s, cut to the features that change everyday code:

Level What it adds One instruction it allows
baseline SSE, SSE2, CMOV, CMPXCHG8B, SYSCALL cvtss2si
x86-64-v2 SSE3 to SSE4.2, SSSE3, POPCNT, CMPXCHG16B, LAHF-SAHF popcnt
x86-64-v3 AVX, AVX2, BMI1, BMI2, FMA, F16C, LZCNT, MOVBE vfmadd132pd
x86-64-v4 AVX-512 F, BW, CD, DQ and VL kmovw

A compiler reads the level as a list of switches. The first half of the session prints the feature macros clang defines for each -march level, and the second half compiles three small functions from level.c for the baseline and for v3:

level.c: a shift, a multiply-add and a 16-element loop C · level.c
/* Three small functions whose best instructions depend on the x86-64 level the compiler may assume. */
unsigned shift_left(unsigned x, unsigned n) { return x << (n & 31); }

float scale_add(float a, float b, float c) { return a * b + c; }

void add_arrays(float *restrict out, const float *restrict a, const float *restrict b) {
    for (int i = 0; i < 16; i++) out[i] = a[i] + b[i];
}
What each level turns on, and what changes in the code Python · levels_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

FEATURES = "'__(SSE2|SSE3|SSSE3|SSE4_1|SSE4_2|POPCNT|AVX|AVX2|BMI|BMI2|F16C|FMA|LZCNT|MOVBE|AVX512(F|BW|CD|DQ|VL))__'"

session.run(f"""
# The feature macros clang defines for each level: what code built for it may use without asking
clang-23 --target=x86_64-linux-gnu -march=x86-64 -dM -E -x c /dev/null | grep -oE {FEATURES} | sort | tr '\\n' ' '
clang-23 --target=x86_64-linux-gnu -march=x86-64-v2 -dM -E -x c /dev/null | grep -oE {FEATURES} | sort | tr '\\n' ' '
clang-23 --target=x86_64-linux-gnu -march=x86-64-v3 -dM -E -x c /dev/null | grep -oE {FEATURES} | sort | tr '\\n' ' '
clang-23 --target=x86_64-linux-gnu -march=x86-64-v4 -dM -E -x c /dev/null | grep -oE {FEATURES} | sort | tr '\\n' ' '
""")

session.run("""
# The same three functions for the baseline and for x86-64-v3: the shift, the multiply-add and the loop's width
clang-23 --target=x86_64-linux-gnu -O2 -march=x86-64 -masm=intel -S -o - level.c | grep -E '^\\s+(shl|mulss|addss|addps)'
clang-23 --target=x86_64-linux-gnu -O2 -march=x86-64-v3 -masm=intel -S -o - level.c | grep -E '^\\s+(shlx|vfmadd|vaddps)'
# x86-64-v4 allows 512-bit vectors, but the loop stays at 256 bits until the build asks for 512
clang-23 --target=x86_64-linux-gnu -O2 -march=x86-64-v4 -masm=intel -S -o - level.c | grep -E '^\\s+vaddps'
clang-23 --target=x86_64-linux-gnu -O2 -march=x86-64-v4 -mprefer-vector-width=512 -masm=intel -S -o - level.c | grep -E '^\\s+vaddps'
""")

Output

# The feature macros clang defines for each level: what code built for it may use without asking
$ clang-23 --target=x86_64-linux-gnu -march=x86-64 -dM -E -x c /dev/null | grep -oE '__(SSE2|SSE3|SSSE3|SSE4_1|SSE4_2|POPCNT|AVX|AVX2|BMI|BMI2|F16C|FMA|LZCNT|MOVBE|AVX512(F|BW|CD|DQ|VL))__' | sort | tr '\n' ' '
__SSE2__
$ clang-23 --target=x86_64-linux-gnu -march=x86-64-v2 -dM -E -x c /dev/null | grep -oE '__(SSE2|SSE3|SSSE3|SSE4_1|SSE4_2|POPCNT|AVX|AVX2|BMI|BMI2|F16C|FMA|LZCNT|MOVBE|AVX512(F|BW|CD|DQ|VL))__' | sort | tr '\n' ' '
__POPCNT__ __SSE2__ __SSE3__ __SSE4_1__ __SSE4_2__ __SSSE3__
$ clang-23 --target=x86_64-linux-gnu -march=x86-64-v3 -dM -E -x c /dev/null | grep -oE '__(SSE2|SSE3|SSSE3|SSE4_1|SSE4_2|POPCNT|AVX|AVX2|BMI|BMI2|F16C|FMA|LZCNT|MOVBE|AVX512(F|BW|CD|DQ|VL))__' | sort | tr '\n' ' '
__AVX__ __AVX2__ __BMI__ __BMI2__ __F16C__ __FMA__ __LZCNT__ __MOVBE__ __POPCNT__ __SSE2__ __SSE3__ __SSE4_1__ __SSE4_2__ __SSSE3__
$ clang-23 --target=x86_64-linux-gnu -march=x86-64-v4 -dM -E -x c /dev/null | grep -oE '__(SSE2|SSE3|SSSE3|SSE4_1|SSE4_2|POPCNT|AVX|AVX2|BMI|BMI2|F16C|FMA|LZCNT|MOVBE|AVX512(F|BW|CD|DQ|VL))__' | sort | tr '\n' ' '
__AVX__ __AVX2__ __AVX512BW__ __AVX512CD__ __AVX512DQ__ __AVX512F__ __AVX512VL__ __BMI__ __BMI2__ __F16C__ __FMA__ __LZCNT__ __MOVBE__ __POPCNT__ __SSE2__ __SSE3__ __SSE4_1__ __SSE4_2__ __SSSE3__
# The same three functions for the baseline and for x86-64-v3: the shift, the multiply-add and the loop's width
$ clang-23 --target=x86_64-linux-gnu -O2 -march=x86-64 -masm=intel -S -o - level.c | grep -E '^\s+(shl|mulss|addss|addps)'
	shl	eax, cl
	mulss	xmm0, xmm1
	addss	xmm0, xmm2
	addps	xmm1, xmm0
	addps	xmm1, xmm0
	addps	xmm1, xmm0
	addps	xmm1, xmm0
$ clang-23 --target=x86_64-linux-gnu -O2 -march=x86-64-v3 -masm=intel -S -o - level.c | grep -E '^\s+(shlx|vfmadd|vaddps)'
	shlx	eax, edi, esi
	vfmadd213ss	xmm0, xmm1, xmm2        # xmm0 = (xmm1 * xmm0) + xmm2
	vaddps	ymm0, ymm0, ymmword ptr [rdx]
	vaddps	ymm0, ymm0, ymmword ptr [rdx + 32]
# x86-64-v4 allows 512-bit vectors, but the loop stays at 256 bits until the build asks for 512
$ clang-23 --target=x86_64-linux-gnu -O2 -march=x86-64-v4 -masm=intel -S -o - level.c | grep -E '^\s+vaddps'
	vaddps	ymm0, ymm0, ymmword ptr [rdx]
	vaddps	ymm0, ymm0, ymmword ptr [rdx + 32]
$ clang-23 --target=x86_64-linux-gnu -O2 -march=x86-64-v4 -mprefer-vector-width=512 -masm=intel -S -o - level.c | grep -E '^\s+vaddps'
	vaddps	zmm0, zmm0, zmmword ptr [rdx]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 levels_session.py

Each level’s line is a superset of the one before: the baseline defines only __SSE2__ of the macros asked for, v2 adds five, v3 adds eight more and v4 adds the five AVX-512 subsets. The code follows. The variable shift becomes one shlx instead of a move into cl and a shl, the multiply and add become one vfmadd213ss, and the loop’s four 128-bit addps become two 256-bit vaddps.

The v4 lines carry a detail worth remembering. The level allows 512-bit vectors, but clang kept the loop at 256 bits until the build added -mprefer-vector-width=512; only then did it emit one vaddps on zmm0. A level states what the processor has, while the compiler’s tuning still decides what is worth using.

Key idea

A level is a contract for a whole set of features at once: build for x86-64-v3 and the code may use AVX2, FMA and BMI2 everywhere, but it then needs a v3 processor to run at all.

Distributions ship the baseline and a faster copy

A v3 build cannot run on an older processor: the psABI warns that loading such a library fails there, and code that does start stops with SIGILL at the first instruction the processor lacks. So a distribution cannot simply switch its whole archive to v3. The psABI describes the usual way out: install the optimised shared object in a glibc-hwcaps folder: A subfolder of a library folder named after a hardware level, such as glibc-hwcaps/x86-64-v3. Since glibc 2.33 the dynamic linker searches the folders of the levels the processor supports, best first, before the default folder. folder named after its level, and keep a baseline build in the default folder. Since glibc 2.33 the dynamic linker searches those folders on its own, best level first.

Step through one load on a processor that reaches x86-64-v3 but has no AVX-512:

Interactive · stage by stage Where the loader looks for a library on an x86-64-v3 machine Step through one library load and watch the loader skip the level this processor lacks.
Where the loader looks for a library on an x86-64-v3 machine, step 6 of 6. Showing Program needs libdemo, glibc-hwcaps/x86-64-v4 (not searched: no AVX-512), glibc-hwcaps/x86-64-v3 (found and loaded), glibc-hwcaps/x86-64-v2, Default library folder (baseline build).Program needs libdemolibdemo.so.1glibc-hwcaps/x86-64-v4not searched: no AVX-512glibc-hwcaps/x86-64-v3found and loadedglibc-hwcaps/x86-64-v2Default library folderbaseline build

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. A library is needed. The program lists libdemo.so.1 among the libraries it needs. The package installed two builds of it, one built for the baseline in the default folder and one built with -march=x86-64-v3.
  2. The processor sets the list. This processor has every x86-64-v3 feature but not AVX512F, so the dynamic linker searches the v3 folder, then the v2 folder, then the default folder, in that order.
  3. The v4 folder is left out. x86-64-v4 needs AVX-512, which this processor lacks, so its folder is not on the list at all. A v4 build placed there could never be loaded by mistake on this machine.
  4. The v3 copy is found. The v3 folder holds libdemo.so.1, so the loader maps that copy. Its code may use AVX2, FMA and BMI2 without testing for them first, because the level promised they exist.
  5. The rest is not read. Once a copy is found the search stops, so the v2 folder and the default folder are not read for this library. A machine without x86-64-v3 would load the default copy instead.
  6. Why the baseline copy stays. The psABI asks for the fallback in the default folder to be built for the baseline, so the same package still loads on a processor that reaches none of the levels.

The search list is computed from the processor, so the v4 folder never appears on it and the v3 copy wins. The same package loads its baseline copy on a machine that stops at v2, and nobody has to detect anything.

Distributions use the levels in three ways. Some require a level for the whole system, which ends support for older processors. Some ship optimised copies of a few libraries in glibc-hwcaps folders, as above. Ubuntu added a third in 25.10: an architecture variant, a second build of whole packages. Its 26.04 LTS release notes describe amd64v3, a variant optimised for x86-64-v3 that is opt-in by default; the notes switch it on with one APT setting, APT::Architecture-Variants. The same notes say Ubuntu’s 26.04 cloud images are built with AMD64v3 by default.

Copying a v3 library into the default folder

Replacing the baseline copy with the v3 build makes the package fail on every processor below v3, at load time or at the first AVX2 instruction. Put the v3 build in glibc-hwcaps/x86-64-v3 beside the baseline copy, which the psABI asks to stay built with -march=x86-64.

Checkpoint

Check: What a micro-architecture level promises · How distributions ship level builds

  1. Question 1 of 2 A library is built with -march=x86-64-v3. Which processor can run every function in it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: One that has every x86-64-v3 feature, whether or not it also has AVX-512

    A level names a set of features, and each level includes the ones below it. Code built for x86-64-v3 may use AVX, AVX2, BMI1, BMI2, FMA, F16C, LZCNT and MOVBE anywhere, so it needs a processor with all of them.

  2. Question 2 of 2 A processor reaches x86-64-v2 but not v3. glibc-hwcaps/x86-64-v3 and the default folder both hold libdemo.so.1. Which copy loads?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The copy in the default folder, because the v3 folder is not on this processor's search list

    From glibc 2.33 the dynamic linker builds its glibc-hwcaps search list from the levels the processor supports, best first, and ends with the default folder. A v2 processor never searches the v3 folder.

APX adds sixteen registers and a destination operand

Intel APX, the Advanced Performance Extensions, adds the extended general-purpose registers: The sixteen general-purpose registers r16 to r31 that Intel APX adds to x86-64's rax to r15, reached through the REX2 prefix or an extended EVEX prefix. r16 to r31, three-operand forms with a new data destination: An Intel APX form in which an instruction such as add writes its result to a third register and only reads its two sources, as in add r20, r21, r22., forms that leave the flags untouched, push2 and pop2, and conditional compares. Two prefixes carry it. An existing one- or two-byte-opcode instruction reaches r16 to r31 through the two-byte REX2 prefix: A two-byte x86-64 prefix defined by Intel APX: the byte 0xD5 and a payload byte whose bits extend register numbers to five bits, so one- and two-byte-opcode instructions can name r16 to r31., and what REX2 cannot express, such as a new data destination, goes through an extended EVEX prefix, the four-byte prefix AVX-512 introduced.

NASM 3.00 added APX, so NASM 3.02 assembles apx.asm, and LLVM’s disassembler reads each instruction back:

apx.asm: six APX forms ASM · apx.asm
; Intel APX forms for NASM 3.02 in a 64-bit ELF object: the lesson reads their bytes.
bits 64
section .text
global apx_forms
apx_forms:
    add r20, r21           ; an old two-operand add on extended registers
    add r20, r21, r22      ; new data destination: r20 = r21 + r22
    {nf} add rax, rbx      ; the same add without writing the flags
    push2 r16, r17         ; two registers saved by one instruction
    pop2 r17, r16          ; and restored in the opposite order
    mov r31, [r30 + 8]     ; a load with extended base and destination
    ret
The bytes of each APX form, and two encodings of one add Python · apx_bytes_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

session.run("""
# NASM 3.02 assembles the APX forms; LLVM's disassembler reads every byte back (AT&T order: destination last)
nasm -f elf64 apx.asm -o apx.o
llvm-objdump -d --no-addresses apx.o | grep -E '^\\s+[0-9a-f]{2} '
# LLVM's assembler picks a different, equally valid encoding for the three-operand add
echo 'add r20, r21, r22' | llvm-mc -triple=x86_64 -x86-asm-syntax=intel -output-asm-variant=1 -show-encoding
echo 'add r20, r21, r22' | llvm-mc -triple=x86_64 -x86-asm-syntax=intel -filetype=obj -o add.o
llvm-objdump -d --no-addresses add.o | grep -E '^\\s+[0-9a-f]{2} '
""")

Output

# NASM 3.02 assembles the APX forms; LLVM's disassembler reads every byte back (AT&T order: destination last)
$ nasm -f elf64 apx.asm -o apx.o
$ llvm-objdump -d --no-addresses apx.o | grep -E '^\s+[0-9a-f]{2} '
 d5 58 01 ec                           	addq	%r21, %r20
 62 ec dc 10 03 ee                     	addq	%r22, %r21, %r20
 62 f4 fc 0c 03 c3                     	{nf}	addq	%rbx, %rax
 62 fc 7c 10 ff f1                     	push2	%r17, %r16
 62 fc 74 10 8f c0                     	pop2	%r16, %r17
 d5 5d 8b 7e 08                        	movq	0x8(%r30), %r31
 c3                                    	retq
# LLVM's assembler picks a different, equally valid encoding for the three-operand add
$ echo 'add r20, r21, r22' | llvm-mc -triple=x86_64 -x86-asm-syntax=intel -output-asm-variant=1 -show-encoding
	add	r20, r21, r22                   # encoding: [0x62,0xec,0xdc,0x10,0x01,0xf5]
$ echo 'add r20, r21, r22' | llvm-mc -triple=x86_64 -x86-asm-syntax=intel -filetype=obj -o add.o
$ llvm-objdump -d --no-addresses add.o | grep -E '^\s+[0-9a-f]{2} '
 62 ec dc 10 01 f5                     	addq	%r22, %r21, %r20

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 apx_bytes_session.py

The two-operand add r20, r21 is four bytes, d5 58 01 ec: REX2, then the ordinary ADD opcode and its ModRM byte. The three-operand add, the no-flags add, push2 and pop2 start with 62, the EVEX prefix, and take six bytes; the load from [r30 + 8] needs only REX2 again, five bytes with its displacement. Step through the four bytes to see where the fifth register bit of each operand hides:

Interactive · stage by stage Reading the four bytes of add r20, r21 Step through the bytes in order; each step names the bits one byte carries.
Reading the four bytes of add r20, r21, step 6 of 6. Showing d5: REX2 prefix, 58: REX2 payload (M0 0, R4 1, B4 1, W 1), 01: opcode ADD, ec: ModRM byte (mod 11, reg 101, rm 100), add r20, r21.d5: REX2 prefix58: REX2 payloadM0 0, R4 1, B4 1, W 101: opcode ADDec: ModRM bytemod 11, reg 101, rm 100add r20, r21

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The prefix byte. The byte 0xD5 starts a REX2 prefix. The APX specification makes REX2 two bytes long, this byte and one payload byte, and it is valid only in 64-bit mode with APX enabled.
  2. The payload bits. 0x58 is 0101 1000, read as M0, R4, X4, B4, W, R3, X3 and B3. M0 = 0 picks the one-byte opcode map, W = 1 makes the add 64 bits wide, and R4 = 1 and B4 = 1 are fifth register bits.
  3. The opcode. 0x01 is the ordinary ADD that adds a register into a register or memory operand. REX2 changes which registers it can name, not what the opcode does.
  4. The ModRM byte. 0xEC is 11 101 100. Mod 11 says both operands are registers; reg = 101 and r/m = 100 are the low three bits of the source and destination register numbers.
  5. Join the bits. The source in ModRM.reg joins R4, R3 and its three bits, 1, 0, 101, so it is register 21. The destination in ModRM.r/m joins B4, B3 and its three bits, 1, 0, 100, so it is register 20. The bytes say add r20, r21.
  6. What the extra byte buys. The same add of two original registers needs a one-byte REX prefix; REX2 costs one more byte and lets either operand be any of 32 registers. Three-operand forms need the six-byte EVEX encoding instead.

The last lines of the session hold a surprise worth keeping. NASM encoded add r20, r21, r22 with opcode 03, LLVM with 01 and a different ModRM byte, and both disassemble to the same instruction. ADD has two opcodes, 01 and 03, that differ only in which ModRM field names which operand. With a new destination both name two sources and add them, so two correct assemblers may give different bytes. Compare encodings by their disassembly, never byte for byte.

Linux and Windows split r16 to r31 differently

Sixteen more registers help only if the ABI lets a function use them cheaply. The two x86-64 ABIs disagree:

Registers System V (Linux and other Unix-like systems) Microsoft x64 (Windows)
r16 to r29 caller-saved: any call may change them volatile: any call may change them
r30 and r31 caller-saved nonvolatile: a function that writes them saves them first

The psABI keeps only rbp, rbx and r12 to r15 for the caller, so r16 to r31 belong to the called function, and a Linux function may write all of them without saving anything. Microsoft keeps r30 and r31 callee-saved register: A register whose value a called function must hand back unchanged: if it writes the register, it saves the old value first and restores it before it returns. rbx on x86-64, x19 on AArch64 and s0 on RISC-V are callee-saved. More in “Callee-saved registers and stack frames”, and adds a rule for code between setjmp and longjmp: r30 and r31 are not part of jmp_buf, so such a function must leave them unchanged.

The second session compiles mix28.c, a function with 28 loaded values that all stay live, for both ABIs, with and without -mapxf:

mix28.c: twenty-eight live values C · mix28.c
/* Twenty-eight loaded values that all stay live to the last line: more than sixteen registers can hold. */
long mix28(const long *p) {
    long v0 = p[0], v1 = p[1], v2 = p[2], v3 = p[3], v4 = p[4], v5 = p[5], v6 = p[6];
    long v7 = p[7], v8 = p[8], v9 = p[9], v10 = p[10], v11 = p[11], v12 = p[12], v13 = p[13];
    long v14 = p[14], v15 = p[15], v16 = p[16], v17 = p[17], v18 = p[18], v19 = p[19], v20 = p[20];
    long v21 = p[21], v22 = p[22], v23 = p[23], v24 = p[24], v25 = p[25], v26 = p[26], v27 = p[27];
    long s = v0 * v1 + v1 * v2 + v2 * v3 + v3 * v4 + v4 * v5 + v5 * v6 + v6 * v7
           + v7 * v8 + v8 * v9 + v9 * v10 + v10 * v11 + v11 * v12 + v12 * v13 + v13 * v14
           + v14 * v15 + v15 * v16 + v16 * v17 + v17 * v18 + v18 * v19 + v19 * v20 + v20 * v21
           + v21 * v22 + v22 * v23 + v23 * v24 + v24 * v25 + v25 * v26 + v26 * v27 + v27 * v0;
    long x = v0 ^ v1 ^ v2 ^ v3 ^ v4 ^ v5 ^ v6 ^ v7 ^ v8 ^ v9 ^ v10 ^ v11 ^ v12 ^ v13
           ^ v14 ^ v15 ^ v16 ^ v17 ^ v18 ^ v19 ^ v20 ^ v21 ^ v22 ^ v23 ^ v24 ^ v25 ^ v26 ^ v27;
    return s ^ x;
}
The same function with 16 and with 32 registers, for Linux and for Windows Python · apx_abi_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

session.run("""
# 28 live values: the instructions, then the spill and reload slots, without APX and with it (System V, Linux)
clang-23 --target=x86_64-linux-gnu -O2 -S -o - mix28.c | grep -cE '^\\s+[a-z{]'
clang-23 --target=x86_64-linux-gnu -O2 -S -o - mix28.c | grep -cE 'Spill|Reload'
clang-23 --target=x86_64-linux-gnu -O2 -mapxf -S -o - mix28.c | grep -cE '^\\s+[a-z{]'
clang-23 --target=x86_64-linux-gnu -O2 -mapxf -S -o - mix28.c | grep -cE 'Spill|Reload'
# The registers each ABI makes the function save: Linux keeps r16-r31 free, Windows saves r30 and r31
clang-23 --target=x86_64-linux-gnu -O2 -mapxf -masm=intel -S -o - mix28.c | grep -E '^\\s+push'
clang-23 --target=x86_64-pc-windows-msvc -O2 -mapxf -masm=intel -S -o - mix28.c | grep -E '^\\s+push'
""")

Output

# 28 live values: the instructions, then the spill and reload slots, without APX and with it (System V, Linux)
$ clang-23 --target=x86_64-linux-gnu -O2 -S -o - mix28.c | grep -cE '^\s+[a-z{]'
133
$ clang-23 --target=x86_64-linux-gnu -O2 -S -o - mix28.c | grep -cE 'Spill|Reload'
38
$ clang-23 --target=x86_64-linux-gnu -O2 -mapxf -S -o - mix28.c | grep -cE '^\s+[a-z{]'
117
$ clang-23 --target=x86_64-linux-gnu -O2 -mapxf -S -o - mix28.c | grep -cE 'Spill|Reload'
6
# The registers each ABI makes the function save: Linux keeps r16-r31 free, Windows saves r30 and r31
$ clang-23 --target=x86_64-linux-gnu -O2 -mapxf -masm=intel -S -o - mix28.c | grep -E '^\s+push'
	pushp	rbp
	push2p	r15, r14
	push2p	r13, r12
	pushp	rbx
$ clang-23 --target=x86_64-pc-windows-msvc -O2 -mapxf -masm=intel -S -o - mix28.c | grep -E '^\s+push'
	pushp	r31
	push2p	r30, r15
	push2p	r14, r13
	push2p	r12, rsi
	push2p	rdi, rbp
	pushp	rbx

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 apx_abi_session.py

With 16 registers the Linux build has 133 instructions and 38 spills and reloads, the stack stores and loads the compiler marks when values do not fit in registers. With APX it has 117 instructions and 6 of them, and it uses r16 to r31 without saving any: its prologue saves only the six classic callee-saved registers, in four pushp and push2p instructions, where the final p is the push-pop hint the APX specification defines for paired saves and restores. The Windows build saves r31 and r30 as well, together with rsi and rdi, which Microsoft also keeps callee-saved, because the function used them.

Key idea

APX doubles the general-purpose registers, and the ABI decides what they cost: on Linux a function may write all sixteen without saving them, on Windows r30 and r31 must be saved and restored like rbx.

Checkpoint

Check: APX registers and encodings · The APX registers in the two ABIs

  1. Question 1 of 3 How many bytes does the recorded add r20, r21 take, REX2 prefix included?

    Type a number.

    Show the answer: Question 1 of 3

    Answer: 4 bytes

    The listing shows d5 58 01 ec: the two-byte REX2 prefix (0xD5 and its payload byte), the ADD opcode 0x01 and the ModRM byte 0xEC.

  2. Question 2 of 3 NASM gives 62 ec dc 10 03 ee for add r20, r21, r22 and LLVM gives 62 ec dc 10 01 f5. What do you conclude?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: Both are correct: ADD has two opcodes that place the sources in different ModRM fields

    Compare encodings by decoding them. ADD r/m, reg (01) and ADD reg, r/m (03) both exist; with the APX new data destination both forms add their two ModRM operands into the register named by EVEX.

  3. Question 3 of 3 A Windows x64 function built for APX writes r30. What must it do?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: Save r30 before writing it and restore it before returning, as the recorded prologue does

    Microsoft's x64 conventions list R16 to R29 as volatile and R30 and R31 as nonvolatile. The Windows build of mix28 saved r31 and r30 in its prologue, while the Linux build saved neither.

AVX10 turns AVX-512 into one numbered feature

AVX-512 brought 512-bit registers, 32 vector registers and eight mask registers, then grew one CPUID flag per family of instructions until code had to test a long list before using any of them. Intel’s answer is AVX10: Intel's converged vector instruction set, based on AVX-512 and including all of its instructions, enumerated by one CPUID flag and an inclusive version number instead of one flag per instruction family.. The SDM’s chapter on it states that AVX10 is based on AVX-512 and includes all of its instructions, and that support is read as one CPUID flag in leaf 7 plus a version number in leaf 24H. Versions are inclusive, so version N+1 has everything in version N.

Walk through the vector extensions in order:

Timeline · 7 events in order x86-64 vector extensions, from SSE2 to AVX10.2 Walk through the extensions in order; the tag says which level or version brought each one.
  1. SSE2

    The 128-bit floor of every x86-64 processor baseline

    SSE2 is part of the psABI baseline, so any x86-64 program may use the 128-bit xmm registers without testing for them.

  2. SSE4.2, POPCNT

    More 128-bit operations and a bit count x86-64-v2

    SSE3, SSSE3, SSE4.1, SSE4.2 and POPCNT join CMPXCHG16B and LAHF-SAHF in x86-64-v2, still with 128-bit registers.

  3. AVX, AVX2

    256-bit ymm registers through the VEX prefix x86-64-v3

    AVX widened the vector registers to 256 bits with the VEX prefix and AVX2 brought integer work to that width. With FMA, BMI1, BMI2, F16C, LZCNT and MOVBE they make up x86-64-v3.

  4. AVX-512

    512 bits, 32 vector registers and 8 masks x86-64-v4

    AVX-512 added zmm registers, vector registers 16 to 31 and the opmask registers k0 to k7, and vector instructions reach all three through the EVEX prefix. Its F, CD, BW, DQ and VL subsets make up x86-64-v4.

  5. More AVX-512

    One CPUID flag for every new family

    Later processors added VBMI, IFMA, VNNI, BF16, VPOPCNTDQ, VBMI2, BITALG and FP16, each with a feature flag of its own, so code tested every family it used.

  6. AVX10.1

    The AVX-512 set under one version number new enumeration

    AVX10 version 1 gathers those families into one feature with a version number, read from CPUID leaf 24H, so a program checks for AVX10 and a version at least N instead of a list of flags.

  7. AVX10.2

    New instructions, every vector width new instructions

    AVX10.2 adds instructions such as vminmaxps, and every processor that reports AVX10 supports all three vector widths. Intel adds new vector features only to AVX10 from here on.

The timeline has 7 events, from SSE2 to AVX10.2.

The encodings show why EVEX is the prefix that matters. The same vaddps ymm0, ymm1, ymm2 is four bytes in VEX and six when forced into EVEX; registers 16 to 31, masking with k1 and AVX10.2’s vminmaxps exist only in EVEX:

vec.asm: VEX, EVEX, a mask and an AVX10.2 instruction ASM · vec.asm
; Vector encodings for NASM 3.02 in a 64-bit ELF object: the lesson reads their bytes.
bits 64
section .text
global vector_forms
vector_forms:
    vaddps ymm0, ymm1, ymm2          ; AVX: a VEX prefix
    {evex} vaddps ymm0, ymm1, ymm2   ; the same operation in EVEX
    vaddps ymm16, ymm17, ymm18       ; registers 16-31 exist only in EVEX
    vaddps zmm0{k1}{z}, zmm1, zmm2   ; 512 bits, mask k1, zeroing
    vminmaxps zmm0, zmm1, zmm2, 0    ; an AVX10.2 instruction
    ret
Vector encodings, and what clang assumes for v4, AVX10.1 and AVX10.2 Python · vectors_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

FEATURES = "'__(AVX512(F|BW|CD|DQ|VL)|AVX10_[12](_512)?)__'"

session.run("""
# One vaddps in two encodings, the registers only EVEX can name, a mask, and an AVX10.2 instruction
nasm -f elf64 vec.asm -o vec.o
llvm-objdump -d -M intel --no-addresses vec.o | grep -E '^\\s+[0-9a-f]{2} '
""")

session.run(f"""
# What clang may assume for x86-64-v4, for AVX10.1 and for AVX10.2
clang-23 --target=x86_64-linux-gnu -march=x86-64-v4 -dM -E -x c /dev/null | grep -oE {FEATURES} | sort | tr '\\n' ' '
clang-23 --target=x86_64-linux-gnu -mavx10.1 -dM -E -x c /dev/null | grep -oE {FEATURES} | sort | tr '\\n' ' '
clang-23 --target=x86_64-linux-gnu -mavx10.2 -dM -E -x c /dev/null | grep -oE {FEATURES} | sort | tr '\\n' ' '
""")

Output

# One vaddps in two encodings, the registers only EVEX can name, a mask, and an AVX10.2 instruction
$ nasm -f elf64 vec.asm -o vec.o
$ llvm-objdump -d -M intel --no-addresses vec.o | grep -E '^\s+[0-9a-f]{2} '
 c5 f4 58 c2                           	vaddps	ymm0, ymm1, ymm2
 62 f1 74 28 58 c2                     	vaddps	ymm0, ymm1, ymm2
 62 a1 74 20 58 c2                     	vaddps	ymm16, ymm17, ymm18
 62 f1 74 c9 58 c2                     	vaddps	zmm0 {k1} {z}, zmm1, zmm2
 62 f3 75 48 52 c2 00                  	vminmaxps	zmm0, zmm1, zmm2, 0x0
 c3                                    	ret
# What clang may assume for x86-64-v4, for AVX10.1 and for AVX10.2
$ clang-23 --target=x86_64-linux-gnu -march=x86-64-v4 -dM -E -x c /dev/null | grep -oE '__(AVX512(F|BW|CD|DQ|VL)|AVX10_[12](_512)?)__' | sort | tr '\n' ' '
__AVX512BW__ __AVX512CD__ __AVX512DQ__ __AVX512F__ __AVX512VL__
$ clang-23 --target=x86_64-linux-gnu -mavx10.1 -dM -E -x c /dev/null | grep -oE '__(AVX512(F|BW|CD|DQ|VL)|AVX10_[12](_512)?)__' | sort | tr '\n' ' '
__AVX10_1__ __AVX10_1_512__ __AVX512BW__ __AVX512CD__ __AVX512DQ__ __AVX512F__ __AVX512VL__
$ clang-23 --target=x86_64-linux-gnu -mavx10.2 -dM -E -x c /dev/null | grep -oE '__(AVX512(F|BW|CD|DQ|VL)|AVX10_[12](_512)?)__' | sort | tr '\n' ' '
__AVX10_1__ __AVX10_1_512__ __AVX10_2__ __AVX10_2_512__ __AVX512BW__ __AVX512CD__ __AVX512DQ__ __AVX512F__ __AVX512VL__

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 vectors_session.py

The macro lines show the overlap in a compiler’s terms: of the macros the session asks for, -mavx10.1 turns on the same five AVX-512 subsets as x86-64-v4 and adds __AVX10_1__, and -mavx10.2 adds __AVX10_2__ on top. One change sits in the AVX10 technical paper’s own history. Its earlier revisions referred to a 256-bit maximum vector size and to enumerating vector lengths; revision 3.0 removed both, and the SDM now says every processor that reports AVX10 supports all three widths.

New vector features arrive only in AVX10

The SDM says new vector instructions will be added only to AVX10 from now on, so a program that needs a recent one tests for AVX10 and a version, not for another AVX-512 flag.

Checkpoint

Check: From AVX-512 flags to AVX10 versions

  1. Question 1 of 2 A program needs an instruction that AVX10.2 introduced. What should it test before using it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: That the processor reports AVX10 and a version of 2 or more

    AVX10 support is one CPUID flag plus a version in leaf 24H, and version N+1 includes version N, so a single comparison covers every instruction up to the version needed.

  2. Question 2 of 2 Which of these recorded vector instructions could not be encoded with the VEX prefix?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: vaddps ymm16, ymm17, ymm18

    The same vaddps ymm0, ymm1, ymm2 appears twice, in VEX (four bytes) and forced into EVEX (six). Registers 16 to 31, masking with k1, zmm registers and AVX10.2 instructions such as vminmaxps exist only in EVEX.

Practise

Each task changes a copy of one of this page’s files and re-runs its session:

  1. Add unsigned count(unsigned long x) { return __builtin_popcountl(x); } to level.c and compile it for the baseline and for x86-64-v2. Predict which build gets one popcnt, then check.
  2. In apx.asm, change add r20, r21 to add rax, rbx and compare the lengths: the REX prefix makes three bytes, REX2 four.
  3. Compile mix28.c with -mapxf for x86_64-pc-windows-msvc again, cut it to 20 values, and see whether r30 and r31 still appear in the prologue.

The questions below ask which level, prefix or extension each instruction needs:

Practice that adapts to you

Practise this lesson

16 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 16 What is the lowest x86-64 level whose builds may use popcnt without a test at run time?

    Choose one answer.

    Show the answer: Question 1 of 16

    Answer: x86-64-v2

    x86-64-v2 adds CMPXCHG16B, LAHF-SAHF, POPCNT, SSE3, SSSE3, SSE4.1 and SSE4.2 to the baseline.

  2. Question 2 of 16 A hot function uses vfmadd231ps, shlx and lzcnt. What is the lowest -march level that allows all three?

    Choose one answer.

    Show the answer: Question 2 of 16

    Answer: x86-64-v3

    x86-64-v3 adds AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT and MOVBE, so vfmadd231ps (FMA), shlx (BMI2) and lzcnt (LZCNT) are all allowed from v3.

  3. Question 3 of 16 A disassembly of a library shows kmovw k1, eax. Which level must the processor reach?

    Choose one answer.

    Show the answer: Question 3 of 16

    Answer: x86-64-v4, because kmovw is an AVX-512 mask instruction

    The opmask registers k0 to k7 are part of AVX-512, so any instruction on them needs AVX512F, which the psABI places in x86-64-v4.

  4. Question 4 of 16 A build for x86-64-v4 shows only ymm registers in its hot loop. What does the recorded session suggest?

    Choose one answer.

    Show the answer: Question 4 of 16

    Answer: The compiler preferred 256-bit vectors; -mprefer-vector-width=512 asks it for zmm

    A level says what the processor has. The compiler still decides what is worth using: clang kept 256-bit vectors at x86-64-v4 and switched to one vaddps on zmm0 only with -mprefer-vector-width=512.

  5. Question 5 of 16 On an x86-64 machine that reaches v4, in which order does glibc's dynamic linker search for a library?

    Choose one answer.

    Show the answer: Question 5 of 16

    Answer: glibc-hwcaps/x86-64-v4, then x86-64-v3, then x86-64-v2, then the default folder

    The glibc hardware-capability scheme (glibc 2.33 and later) adds one folder per supported level, best first, and then the usual folders.

  6. Question 6 of 16 A package installs only a v3 build, in the default folder. What happens on a server that stops at x86-64-v2?

    Choose one answer.

    Show the answer: Question 6 of 16

    Answer: It fails there, at load time or at the first v3 instruction, because no baseline copy exists

    The level build needs its features. A package that wants v3 speed and v2 support ships both: the v3 copy under glibc-hwcaps/x86-64-v3 and a baseline copy built with -march=x86-64 in the default folder.

  7. Question 7 of 16 What is amd64v3 in Ubuntu 26.04 LTS?

    Choose one answer.

    Show the answer: Question 7 of 16

    Answer: An architecture variant: packages built a second time for x86-64-v3, opt-in by default

    Since 25.10 Ubuntu can build a second version of a package, a variant; amd64v3 is optimised for x86-64-v3 and is opt-in by default. Separately, the 26.04 notes say Ubuntu's cloud images for 26.04 are built with AMD64v3 by default.

  8. Question 8 of 16 Which of these are part of Intel APX?

    Choose every answer that is right.

    Show the answer: Question 8 of 16

    Answer:

    • The general-purpose registers r16 to r31
    • push2 and pop2
    • Three-operand forms with a new data destination

    APX adds r16 to r31, new-data-destination forms, flag-suppressing forms, conditional compares, push2 and pop2, and a 64-bit absolute jump. Vector widths are AVX-512's and AVX10's.

  9. Question 9 of 16 In the recorded d5 58 01 ec, which byte supplies the fifth bit of each register number?

    Choose one answer.

    Show the answer: Question 9 of 16

    Answer: 58, the REX2 payload, through R4 and B4

    The source, named by ModRM.reg, joins R4, R3 and the reg bits: 1, 0, 101 is register 21. The destination, named by ModRM.r/m, joins B4, B3 and the r/m bits: 1, 0, 100 is register 20. So the bytes say add r20, r21.

  10. Question 10 of 16 After add r20, r21, r22 runs on an APX processor, which registers have changed?

    Choose one answer.

    Show the answer: Question 10 of 16

    Answer: Only r20 and the flags; r21 and r22 keep their values

    An NDD form writes its result to a third register and leaves both sources intact, which saves the extra move the two-operand form needs when a source is still wanted.

  11. Question 11 of 16 Under the System V ABI, may a function overwrite r16 to r31 without saving them first?

    Choose one answer.

    Show the answer: Question 11 of 16

    Answer: Yes: they belong to the called function, like rax and rcx

    On Linux the recorded APX build used r16 to r31 freely and its prologue saved only rbp, rbx and r12 to r15.

  12. Question 12 of 16 Hand-written Linux code that uses r28 to r31 freely is moved unchanged to Windows. What breaks?

    Choose one answer.

    Show the answer: Question 12 of 16

    Answer: Callers on Windows expect r30 and r31 back unchanged, so their values are lost

    Porting between the ABIs means adding saves of r30 and r31 on the Windows side, and keeping them unchanged between setjmp and the call that leads to longjmp, since jmp_buf does not hold them.

  13. Question 13 of 16 Why did the APX build of mix28 have 6 spills and reloads instead of 38?

    Choose one answer.

    Show the answer: Question 13 of 16

    Answer: Sixteen more registers held live values that otherwise went to the stack

    With 32 registers the compiler keeps far more of the 28 values in registers, so it stores fewer to the stack and loads fewer back. The listing also shrank, from 133 instructions to 117, partly for other reasons, such as push2 saving two registers in one instruction.

  14. Question 14 of 16 What does AVX10 contain, according to the SDM?

    Choose one answer.

    Show the answer: Question 14 of 16

    Answer: All of AVX-512, enumerated by one flag and a version number

    AVX10 keeps every AVX-512 instruction and replaces the long list of AVX-512 feature flags with one AVX10 flag and an inclusive version number in CPUID leaf 24H.

  15. Question 15 of 16 A processor reports AVX10 version 2. Can it run code that needs only AVX10.1?

    Choose one answer.

    Show the answer: Question 15 of 16

    Answer: Yes, because versions are inclusive: version 2 has everything in version 1

    Testing for "AVX10 and a version of at least 1" covers every AVX10.1 instruction on every later version.

  16. Question 16 of 16 The recorded vaddps ymm0, ymm1, ymm2 takes 4 bytes in VEX. How many bytes does the same instruction take when forced into EVEX?

    Type a number.

    Show the answer: Question 16 of 16

    Answer: 6 bytes

    The listing shows 62 f1 74 28 58 c2: the four-byte EVEX prefix, the opcode 58 and the ModRM byte, two bytes more than the VEX form c5 f4 58 c2.

Interview questions

What is x86-64-v3, and what does building a library with -march=x86-64-v3 promise?

x86-64-v3 is the x86-64 psABI micro-architecture level that adds AVX, AVX2, BMI1, BMI2, FMA, F16C, LZCNT and MOVBE to the baseline and to v2. Building with -march=x86-64-v3 lets the compiler use all of them anywhere, with no test at run time, so the library is faster on a processor that has them and fails with SIGILL on one that does not. That is why such a build goes into a glibc-hwcaps/x86-64-v3 folder beside a baseline copy, and the dynamic linker picks it only on a processor that reaches the level.

Two harder questions sit in this module’s bank. One starts from a crash on an older server that no machine in your CI can reproduce; the other keeps values in the APX registers across a call into code built without APX.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    Your Linux library ships a baseline copy and a glibc-hwcaps/x86-64-v3 copy. A customer reports that it crashes on an older server, and you cannot reproduce it, because every machine in your CI reaches x86-64-v3. How would you tell which copy that server loads, and how would you test the copy your CI never runs?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    A hand-written APX routine for Linux keeps eight running totals in r24 to r31 while it calls a comparison callback that customers compile themselves. Every test succeeds, and every test callback was built without APX. Is the routine correct, and how would you write its register use for Linux and for Windows?

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

Key takeaways

  • An x86-64 micro-architecture level names a whole set of features; code built for x86-64-v3 uses AVX2, FMA and BMI2 freely and needs a v3 processor to run.
  • Distributions keep a baseline build and add level-specific copies in glibc-hwcaps folders, or a whole package variant such as Ubuntu’s amd64v3, so older processors keep working.
  • APX reaches r16 to r31 through the two-byte REX2 prefix or an extended EVEX prefix, and two correct assemblers may pick different bytes for the same APX instruction.
  • System V lets a function write all sixteen new registers without saving them, while Microsoft’s ABI makes r30 and r31 callee-saved.
  • AVX10 includes all of AVX-512 behind one feature flag and an inclusive version number, and new vector instructions now arrive only there.

Terms in this lesson

AVX10
Intel's converged vector instruction set, based on AVX-512 and including all of its instructions, enumerated by one CPUID flag and an inclusive version number instead of one flag per instruction family.
extended general-purpose registers
The sixteen general-purpose registers r16 to r31 that Intel APX adds to x86-64's rax to r15, reached through the REX2 prefix or an extended EVEX prefix.
glibc-hwcaps folder
A subfolder of a library folder named after a hardware level, such as glibc-hwcaps/x86-64-v3. Since glibc 2.33 the dynamic linker searches the folders of the levels the processor supports, best first, before the default folder.
micro-architecture level
A named set of x86-64 processor features defined by the psABI: x86-64-v2, v3 and v4. Code built for a level may use all of its features, and those of the levels below it, without testing for them at run time.
new data destination
An Intel APX form in which an instruction such as add writes its result to a third register and only reads its two sources, as in add r20, r21, r22.
REX2 prefix
A two-byte x86-64 prefix defined by Intel APX: the byte 0xD5 and a payload byte whose bits extend register numbers to five bits, so one- and two-byte-opcode instructions can name r16 to r31.

References

Report a problem with this lesson

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.