Instruction
SENDUIPI
Sends a user interprocessor interrupt through the entry of the user-interrupt target table that a register selects.
- Extensions
- Forms
- 1 (1 Legacy)
- Data
- Intel XED v2026.08.23
SENDUIPI interrupts another thread without a system call. Its one operand is a 64-bit register holding an index into the sending thread’s user-interrupt target table (UITT), whatever operand size a prefix asks for. The operating system builds that table and points the IA32_UINTR_TT MSR at it; each 16-byte entry has a valid bit, a user-interrupt vector from 0 to 63 and the address of the receiver’s user posted-interrupt descriptor (UPID). The program only chooses the index, so it can reach exactly the receivers the kernel has set up for it.
What it does
SENDUIPI checks the index against the table’s highest index, in IA32_UINTR_MISC, and the entry for its valid bit and reserved bits, raising #GP(0) on any failure. It then updates the UPID atomically: it sets the bit of the vector in the 64-bit field of posted requests, and if neither the outstanding-notification bit (ON) nor the suppress-notification bit (SN) was set, it sets ON and sends an ordinary IPI, with the notification vector and destination APIC ID that the UPID holds. A second SENDUIPI to the same receiver before it has taken the first only adds its vector to the posted bits: ON keeps it from sending another IPI. SN lets the operating system have the requests posted without any IPI.
Its reads and writes of the table and the UPID count as a supervisor’s, so both can live in pages the program itself cannot read or write.
The receiving end
On the receiving core, an interrupt with the vector the OS named in IA32_UINTR_MISC is a user-interrupt notification: the processor moves the posted bits into its request register and, once the receiver runs at CPL 3 in 64-bit mode with UIF set, delivers the highest vector to the receiver’s handler, which returns with UIRET. CLUI and STUI hold delivery back and allow it again.
In compiled code and on Zen 5
With -muintr, GCC 14 and Clang 23 at -O2 compile _senduipi(i) to
senduipi rdi, the argument’s register. Without UINTR (CPUID leaf 7,
EDX bit 5), with CR4.UINTR clear, or while bit 0 of IA32_UINTR_TT says
that the thread has no table, it raises #UD. This site’s Zen 5 reports
no UINTR, and both recorded cases, with indices 0 and 1, raised #UD.
Forms
| Instruction | Encoding | Requires |
|---|---|---|
SENDUIPI r64 64-bit mode only
SENDUIPI_GPR64u32 | F3 0F C7 /6 | UINTR |
Latency and throughput
| Model | r64 |
|---|---|
| Sapphire Rapids | 100 / 0.25p0156 |
Operation
What the forms do, in pseudocode that our tests run against results recorded on real processors. How to read.
if not cr4 >> 25 & 1: # CR4.UINTR; clear without UINTR
fault(UD)
if not uintr_tt & 1: # no valid target table
fault(UD)
i = src.read() # 64 bits, whatever a 66 says
if i > uintr_misc & 0xFFFFFFFF: # past UITTSZ, the highest index
fault(GP, 0)
e = uintr_tt & ~0xF # the table, 16 bytes an entry
lo = load(e + 16 * i, 64) # V (bit 0), the user vector UV in
upid = load(e + 16 * i + 8, 64) # bits 15:8, the UPID's address
if not lo & 1 or lo & ~0x3F01:
fault(GP, 0) # not valid, or reserved bits set
if upid & 0x3F:
fault(GP, 0) # the UPID not 64-byte aligned
v = lo >> 8 & 0x3F
d = load(upid, 64) # ON (bit 0), SN (1), the vector NV
pir = load(upid + 8, 64) # (23:16) and destination NDST
# (63:32); PIR: one bit a vector
if d & 0xFF00FFFC:
fault(GP, 0) # reserved bits set
notify = d & 3 == 0 # neither outstanding nor suppressed
store(upid + 8, 64, pir | 1 << v)
store(upid, 64, d | 1 if notify else d)
# locked, the read and the writes
if notify:
nv = d >> 16 & 0xFF
if msr[0x1B] >> 10 & 1: # IA32_APIC_BASE.EXTD: x2APIC
send_ipi(nv, d >> 32)
else:
send_ipi(nv, d >> 40 & 0xFF)
# its accesses to the table and the UPID are a supervisor's, at any
# CPLRun on AMD Zen 5 (EPYC 9645): 2 cases. Its cases ran where CPUID reported no UINTR, and every one raised an exception.
Operands
| Operand | Kind | Action |
|---|---|---|
SENDUIPI r64 64-bit mode only | ||
r64 | explicit | read |
Flags
None: XED records no flag that the forms test or change.
Intrinsics
The C intrinsics of Clang 23.1.2 that compile to SENDUIPI, each with the form it compiles to and the Clang options it needs. The forms come from compiling every intrinsic and decoding the result with Intel XED; all intrinsics A–Z explains the method.
| Intrinsic | Compiles to |
|---|---|
_senduipi
void _senduipi(unsigned long long __a) |
|
CPUID and processors
Each form belongs to an XED ISA set. A form can be used when the processor reports every CPUID bit of one of its ISA set's alternatives.
| ISA set | CPUID | Processors |
|---|---|---|
UINTR | UINTR | Arrow Lake, Panther Lake, Nova Lake, Sapphire Rapids, Emerald Rapids, Granite Rapids, Diamond Rapids, Sierra Forest, Clearwater Forest |
More UINTR instructions
Sources
- Intel XED v2026.08.23 (commit
0bcb6237345c): forms, encodings, operands, flags, exception classes, attributes, ISA sets, CPUID bits and chip model. - Clang's x86 intrinsic headers, LLVM 23.1.2 (commit
85ac56026243): the intrinsics, their signatures and doc comments, and the Clang of that release, which compiled each of them. - LLVM 23.1.2's x86 scheduling models (commit
85ac56026243): the latency, reciprocal throughput and ports of each form, as llvm-mca reports them. - Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 2 (325383-093, September 2026): Volume 2B, page 4-620 (SENDUIPI)
- Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3 (325384-093, September 2026)
The tables are derived from Intel XED, Copyright Intel Corporation, licensed under the Apache License 2.0; x86-64.net converted and reformatted them. The intrinsics and their signatures come from Clang and the timings from LLVM's scheduling models, both part of the LLVM Project, licensed under the Apache License 2.0 with LLVM Exceptions. The text is our own.