Inline Assembly, Intrinsics, and Barriers
This chapter covers three ways to reach below C to the machine: inline assembly, compiler intrinsics, and compiler/memory barriers. These are non-portable, but essential for systems, embedded, and performance work.
Why This Matters
C does not expose every CPU instruction or every hardware operation. When you need a specific instruction (SIMD, atomics, special registers), a precise memory ordering, or a hand-tuned sequence, you use these tools. Using them correctly requires knowing exactly what they guarantee.
Prerequisites
c.cpu.6— C-to-assembly.
Core Concept
Inline assembly
GCC/Clang support asm (or __asm__) to embed assembly inside C:
static inline unsigned long read_sp(void)
{
unsigned long sp;
__asm__ volatile("mov %%rsp, %0" : "=r"(sp));
return sp;
}
The syntax has three parts: the instruction template, output operands, and input operands, with clobbers for registers it modifies. volatile prevents the compiler from optimizing the asm away.
Intrinsics
Intrinsics are compiler-provided functions that map to specific instructions or operations. Examples:
__builtin_clz— count leading zeros.__sync_*/__atomic_*— atomic operations (GCC/Clang).- SIMD intrinsics from
<immintrin.h>(SSE/AVX),<arm_neon.h>(NEON).
#include <x86intrin.h>
__m256 a = _mm256_set1_ps(2.0f);
Intrinsics are portable *within* a compiler/architecture, unlike raw asm.
Barriers
- Compiler barrier: prevents the compiler from reordering memory
operations across it, but emits no hardware instruction: asm volatile("" ::: "memory").
- Memory barrier (fence): a hardware instruction (
mfence,dmb,fence)
that orders memory operations at the CPU level.
#include <stdatomic.h>
atomic_thread_fence(memory_order_seq_cst); /* C11 memory fence */
Examples
Reading a control register (x86)
static inline unsigned long read_cr3(void)
{
unsigned long v;
__asm__ volatile("mov %%cr3, %0" : "=r"(v));
return v;
}
Count leading zeros (intrinsic)
#include <stdint.h>
int clz32(uint32_t x)
{
return __builtin_clz(x); /* GCC/Clang */
}
Compiler barrier for MMIO
#define COMPILER_BARRIER() __asm__ volatile("" ::: "memory")
void write_reg(volatile uint32_t *reg, uint32_t v)
{
COMPILER_BARRIER();
*reg = v;
COMPILER_BARRIER();
}
This orders the volatile MMIO access relative to surrounding code in the generated output.
How It Works
Inline asm is passed through to the assembler with operand substitution. Intrinsics are lowered by the compiler into the target instruction or a small code sequence. Barriers constrain the compiler's reordering (compiler barrier) or the CPU's memory reordering (fence instruction).
Variations
Extended asm constraints
GCC extended asm uses constraint letters (r for register, m for memory, i for immediate) and clobber lists. This is powerful but non-portable and error-prone.
C11 atomics vs. compiler intrinsics
Prefer C11 _Atomic and atomic_thread_fence over __sync/__atomic intrinsics for portability, reserving intrinsics for operations the standard does not expose.
Common Mistakes
- Forgetting the
"memory"clobber on a compiler barrier (allowing
reordering).
- Getting the asm operand constraints wrong (wrong register, missing clobber).
- Assuming inline asm is portable.
- Using
volatileinstead of real memory barriers for multithreaded ordering.
Undefined Behavior
- Incorrect inline asm (wrong clobbers/constraints) can produce undefined
behavior or corrupt state. The compiler cannot verify the asm.
Portability
- Inline asm and intrinsics are compiler- and architecture-specific. Use them
only behind feature detection.
Under the Hood
Inline asm is emitted verbatim into the assembly. Intrinsics lower to instructions (or call a compiler helper). Barriers become either nothing (compiler barrier) or a fence instruction (memory barrier).
Practical Usage
- Use intrinsics over raw asm when possible.
- Use compiler barriers for MMIO ordering (
c.emb.2). - Use C11 atomics/fences for cross-thread ordering (
c.conc.7).
Exercises
1. Write a read_sp function using inline asm and verify it prints a stack address. 2. Use __builtin_clz to implement a fast log2. 3. Write a compiler barrier and inspect its effect on generated code.
Deep Challenge
Implement a small spinlock using atomic_flag (C11) and explain why a plain volatile flag is insufficient. Then add the appropriate memory order and explain what hardware fence (if any) is emitted on x86-64 vs. ARM64.
Related Concepts
c.conc.7— fences and memory barriers.c.perf.4— SIMD intrinsics.c.emb.2— MMIO and volatile.
References
- GCC inline asm documentation, architecture intrinsic guides.
Verification
- Inline asm and intrinsics are compiler/architecture-specific.
COMPILER-SPECIFIC
- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Inline assembly
- [ ] Intrinsics
- [ ] Compiler barriers
- [ ] Memory barriers
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.cpu.inline-asm | 0 | 6 |
| c.cpu.intrinsics | 0 | 6 |
| c.cpu.barrier | 0 | 6 |