Is there a good reason why C++ doesn't allow you to constexpr inline assembly? And why are unevaluated inline assembly expressions allowed in C++20?
-
2because the c++ compiler can't check the semantics of the assembler code? – Neil Butterworth Oct 15 '22 at 17:01
-
5I don't think inline assembler is a part of the standard. – 273K Oct 15 '22 at 17:05
-
1It is available again from C++20? https://en.cppreference.com/w/cpp/language/asm but it still is an optional feature. And I think its not constexpr because it is not compiled in the usual sense. In any case I'd not recommend using inline assembly, are you sure you can generate better code then the compiler? Watch this : [What has my compiler done for me lately](https://www.youtube.com/watch?v=bSkpMdDe4g4) by Matt Godbolt (the guy from compiler explorer) – Pepijn Kramer Oct 15 '22 at 17:07
-
1More discussions of reasons not to use inline assembly [here](https://gcc.gnu.org/wiki/DontUseInlineAsm). – David Wohlferd Oct 15 '22 at 17:42
2 Answers
The behavior of constant expressions is governed by the C++ standard which defines the language. The behavior of inline assembly is not. By definition, inline assembly is not C++, so C++ can't say what happens within it.
And why are unevaluated inline assembly expressions allowed in C++20?
For the same reason many constructs are allowed in constexpr
that cannot be evaluated at compile-time. constexpr
functions can be called at runtime, and it'd be nice if the runtime version could invoke inline assembly.

- 449,505
- 63
- 781
- 982
-
1Compilers that support inline asm define its behaviour. The actual reason they choose not to define it in a way compatible with `constexpr` is that real compilers don't optimize your inline asm, including not constant-propagating through it. [When will compilers optimize assembly code in C/C++ source?](https://stackoverflow.com/q/41294779) If you want a constant result, you need `if consteval` or GNU C `if (__builtin_constant_p(x)) return pure_cpp(x); else asm("..." : "+r"(x); }` to not run the inline asm on a constant. – Peter Cordes Oct 17 '22 at 06:07
-
@PeterCordes: "*Compilers that support inline asm define its behaviour.*" But the *standard* doesn't. The only way the standard could define its behavior is to just call it implementation-defined. Which is not something the standard generally does with constant evaluation. Also, you forgot about `if(std::is_constant_evaluated())`. – Nicol Bolas Oct 17 '22 at 13:36
-
It seems pretty clear that the question is (implicitly) asking why `asm()` or `asm{}` don't work with `constexpr` *on real-world compilers that support either of those asm extensions*. It's not a bad idea to remind folks that ISO C++ has nothing to say about it, especially given the question phrasing, but that's a rather boring and uninformative answer if that's *all* you say as an answer to a question explicitly about an extension to ISO C++. If you don't want to make your answer a proper answer, I guess I'll just post my comment as an answer. – Peter Cordes Oct 17 '22 at 13:44
-
@PeterCordes: Why would something whose behavior isn't defined by the standard work to generate compile-time constants "on real-world compilers"? The compiler doesn't even have to be running on the same machine that it is compiling *for*. – Nicol Bolas Oct 17 '22 at 13:46
-
The 2nd half of that comment is finally a reason that makes some sense. But if you were going to attempt it, you'd simulate not actually assemble and run a buffer of machine code; assembly isn't memory-safe and could easily crash the compiler, and might depend on the program's data structures existing. As for the first part, GCC can do constant-propagation through GNU C vector extensions (the same ones that headers use to define `__m128i`). No reason that extensions in general can't be constexpr-compatible, just that inline `asm` in particular doesn't make sense for it. – Peter Cordes Oct 17 '22 at 13:51
-
https://godbolt.org/z/r13re6Y1e shows GCC compiling a `constexpr` function that returns `unsigned int v4ui __attribute__((vector_size(16)))`, which obviously isn't defined by ISO C++. There's no reason for g++ to *not* allow it in constexpr functions, because it already knows how to constant-propagate through basic SIMD operations like element-wise addition, and many of its builtins for special things that intrinsics wrap, like `__builtin_ia32_pcmpeqd128` or `__builtin_ia32_punpckhdq128`. https://gcc.gnu.org/onlinedocs/gcc/x86-Built-in-Functions.html e.g https://godbolt.org/z/41f1rKbsP – Peter Cordes Oct 17 '22 at 14:00
Compilers that support asm()
or asm{}
as an extension choose not to define it in a way compatible with constexpr
. In fact, ISO C++ specifically says they shouldn't, 7.7 [expr.const]
5.27! It's one of the things that prevents an expression from being a constant-expression.
Real compilers don't optimize your inline asm, including not constant-propagating through it. See When will compilers optimize assembly code in C/C++ source? This would defeat the purpose of inline asm, which is to run exactly the instructions you specify. (Or not, if the result isn't needed, for GNU C asm()
without volatile
.)
So they don't know how to constant-evaluate an asm statement; it's the opposite of what makes sense; you're expressing program logic in a totally different language which compilers don't normally have to read as input. This is likely why the ISO C++ standard forbids asm("")
in constant expressions.
It would be a lot of work for compiler writers to implement (for constant propagation even if not constexpr
), basically a second language that they'd have to not only scan for used registers (MSVC or clang -fasm-blocks
), but they'd have to actually interpret / simulate the asm code, including potential loops. And presumably bail out on memory access, unless they could prove an addressing mode would refer to a known constant C object. Sounds like a total mess, definitely not something compilers would want to do.
Use intriniscs
If you want access to machine-specific instructions in a way the compiler can understand and optimize, use intrinsics. See https://gcc.gnu.org/wiki/DontUseInlineAsm for that and other reasons not to use it.
Some intrinsics and builtins are compatible with constexpr
on some compilers, because that makes sense and ISO C++ doesn't prevent implementations from doing so when it makes sense. (Nicol's argument only makes sense for compilers that don't define an asm
extension at all, which is obviously not what you're asking about.)
If you want a compile-time-constant result, you need C++20 std::is_constant_evaluated
/ C++23 if constexpr
, or GNU C __builtin_constant_p(x)
.
// Normally don't actually do this, use C++20 std::popcount with appropriate -march
// or __attribute__((target("popcnt"))) on a function that uses it in a loop.
// (inlining doesn't work between functions with different target options.)
constexpr int popcount(int x)
{
if (__builtin_constant_p(x)) {
return __builtin_popcount(x); // Yes, this GNU extension *is* constexpr compatible
// because compilers know how to popcount at compile time.
} else {
asm("popcnt %0, %0 # from asm statement" : "+r"(x)); // GNU++20 for this to appear in a constexpr function
// still somewhat optimization defeating since I forced same-register to work around the Intel false dependency
// and instead of dialect alternatives for AT&T vs. Intel
return x;
}
}
Example on Godbolt with x86-64 asm: we see the constant arg used the builtin (or we could have used a pure ISO pre-C++ way, like a loop or bithack).
But with a non-constant arg, we see our line of inline-asm use popcnt
(including the comment included in the asm("":)
statement) even though we didn't tell GCC the binary was only going to run on CPUs with that instruction. (Related: What exactly do the gcc compiler switches (-mavx -mavx2 -mavx512f) do? and The Effect of Architecture When Using SSE / AVX Intrinisics - where __builtin_popcount
is like SSE or AVX intrinsics, except it has a fallback to a bithack so it always works, it just doesn't always compile to a single machine instruction.)
int test_pop1() {
return popcount(0x555);
}
#gcc12 -O2 -march=x86-64-v2 -mno-popcnt -std=gnu++20
mov eax, 6 # from the pure C side
ret
# without the if(), we'd get mov eax, 0x555 ; popcnt eax,eax
int test_pop_nonconst(int x) {
return popcount(x);
}
# g++ -O2 without a -march that includes popcnt
mov eax, edi
popcnt eax, eax # from asm statement
ret
int test_builtin_popcount(int x) {
return __builtin_popcount(x);
// popcnt only with -march= new enough. Otherwise bithack in helper function
}
# -O2 with no -march, or with -mno-popcnt
sub rsp, 8
mov edi, edi
call __popcountdi2 # libgcc helper function because popcnt might fault
add rsp, 8
ret
# -O2 -march=x86-64-v2 (SSE4.2 Nehalem baseline, generic tuning)
xor eax, eax # break false dependency in case of Intel
popcnt eax, edi
ret
# -O2 -march=znver2
popcnt eax, edi # Zen doesn't have false dependencies for popcnt
ret

- 328,167
- 45
- 605
- 847
-
FYI: The standard says that `asm` *must not* be evaluated at compile-time, and doing so is therefore ill-formed. It is not presented as "conditionally-supported"; it is straight-up not allowed. A compiler can change this as a compiler extension, but changing a rule of the language is different from adding new functionality (like intrinsics) to the language. – Nicol Bolas Oct 17 '22 at 14:36
-
@NicolBolas: Oh, I'd assumed your answer was correct that the ISO standard had nothing to say on the matter, but that makes sense as ISO C++ does define the existence of a possible `asm("")` keyword. Found the entry in the standard and updated my answer. – Peter Cordes Oct 17 '22 at 14:46