constexpr question

Discussion of chess software programming and technical issues.

Moderator: Ras

Cardoso
Posts: 363
Joined: Thu Mar 16, 2006 7:39 pm
Location: Portugal
Full name: Alvaro Cardoso

constexpr question

Post by Cardoso »

Hi,
If I have:

Code: Select all

constexpr BB64 Rank6 = 0b0000000000000000111111110000000000000000000000000000000000000000ull;
constexpr BB64 Rank7 = 0b0000000011111111000000000000000000000000000000000000000000000000ull;
constexpr BB64 Rank8 = 0b1111111100000000000000000000000000000000000000000000000000000000ull;
And in my code I have something like

Code: Select all

if (somebitboard64 & (Rank6 | Rank7 | Rank8)) {
   // do something
}
Is it 100% safe to assume the c++ compiler will never OR those 3 bitboards together every time it executes that code, but instead inserts a 64bit constant that is the result of (Rank6 | Rank7 | Rank8) to avoid calculate it every time?
Witek
Posts: 87
Joined: Thu Oct 07, 2021 12:48 am
Location: Warsaw, Poland
Full name: Michal Witanowski

Re: constexpr question

Post by Witek »

It should compute OR at compile time. But best way to be 100% sure is to look at the assembly code
Author of Caissa Chess Engine: https://github.com/Witek902/Caissa
expositor
Posts: 60
Joined: Sat Dec 11, 2021 5:03 am
Full name: expositor

Re: constexpr question

Post by expositor »

Code: Select all

#include <cstdint>
#include <cstdio>

constexpr uint64_t Rank6 = 0b0000000000000000111111110000000000000000000000000000000000000000ull;
constexpr uint64_t Rank7 = 0b0000000011111111000000000000000000000000000000000000000000000000ull;
constexpr uint64_t Rank8 = 0b1111111100000000000000000000000000000000000000000000000000000000ull;

void f(uint64_t x)
{
  if (x & (Rank6 | Rank7 | Rank8)) puts("mark");
}
by gcc 12.2 with –O3 becomes

Code: Select all

.LC0:
  .string "mark"
f(unsigned long):
  shr rdi, 40
  jne .L4
  ret
.L4:
  mov edi, OFFSET FLAT:.LC0
  jmp puts
and clang emits the same. You can see this on Compiler Explorer.

Apparently constexpr is required for the compiler to perform the optimization, which at first surprised me, but it makes sense – without seeing the rest of the program, the compiler can't determine whether or not Rank6–8 will be modified by other code. Case in point: if you move the Rank6–8 into the function, you can remove constexpr:

Code: Select all

#include <cstdint>
#include <cstdio>

void f(uint64_t x)
{
  uint64_t Rank6 = 0b0000000000000000111111110000000000000000000000000000000000000000ull;
  uint64_t Rank7 = 0b0000000011111111000000000000000000000000000000000000000000000000ull;
  uint64_t Rank8 = 0b1111111100000000000000000000000000000000000000000000000000000000ull;
  if (x & (Rank6 | Rank7 | Rank8)) puts("mark");
}
compiles to

Code: Select all

f(unsigned long): # @f(unsigned long)
  shr rdi, 40
  jne .LBB0_2
  ret
.LBB0_2:
  lea rdi, [rip + .L.str]
  jmp puts@PLT # TAILCALL
.L.str:
  .asciz "mark"
Rein Halbersma
Posts: 749
Joined: Tue May 22, 2007 11:13 am

Re: constexpr question

Post by Rein Halbersma »

Cardoso wrote: Fri Sep 09, 2022 1:10 am

Code: Select all

if (somebitboard64 & (Rank6 | Rank7 | Rank8)) {
   // do something
}
Is it 100% safe to assume the c++ compiler will never OR those 3 bitboards together every time it executes that code, but instead inserts a 64bit constant that is the result of (Rank6 | Rank7 | Rank8) to avoid calculate it every time?
As of C++17 (gcc-7, clang-3.9 and visual c++ 2017) you can use an init-statement inside the if. So if you just define a local constexpr variable Rank678 that holds the bitwise-OR over all the bitboards, it's guaranteed by the compiler to never be evaulated at runtime. For C++11/C++14 you can define Rank678 just prior to the if-statement of course (but then it's scope is larger than needed).

Code: Select all

if (constexpr auto Rank678 = Rank6 | Rank7 | Rank8; somebitboard64 & Rank678) {
    // do something
} // Rank678 goes out of scope here!
Of course, most optimizing compilers will also do the constant-folding if you just have const instead of constexpr. But it's better to rely on the Standard's guarantee.
Sopel
Posts: 391
Joined: Tue Oct 08, 2019 11:39 pm
Full name: Tomasz Sobczyk

Re: constexpr question

Post by Sopel »

it's enough if they are either const or static
dangi12012 wrote:No one wants to touch anything you have posted. That proves you now have negative reputations since everyone knows already you are a forum troll.

Maybe you copied your stockfish commits from someone else too?
I will look into that.
tcusr
Posts: 325
Joined: Tue Aug 31, 2021 10:32 pm
Full name: tcusr

Re: constexpr question

Post by tcusr »

expositor wrote: Fri Sep 09, 2022 4:14 am without seeing the rest of the program, the compiler can't determine whether or not Rank6–8 will be modified by other code.
constexpr functions/variables are always defined in the place they are declared, so they are always visible by the compiler (either in the source file itself or in a header).
this is why constant-folding is performed even without any optimization.
Sesse
Posts: 300
Joined: Mon Apr 30, 2018 11:51 pm

Re: constexpr question

Post by Sesse »

constexpr has no guarantee of constant-time evaluation. It merely guarantees that the value can be used in certain contexts such as template parameters and array sizes, but that doesn't mean that it has to constant-fold if it doesn't care to. consteval and constinit add yet more guarantees on top of this (for instance, anything constinit will be initialized before any code is run), but you still don't have a guarantee of constant folding. Heck, C++ doesn't even guarantee that “int x = 24;” won't be represented in assembler as a function computing the factorial of 4, recursively (and on certain platforms, larger constants will indeed need to be pieced together by combining multiple loads; are those constant or not? :-) ). For many things, the easiest way to get the behavior the standard mandates is indeed to constant-fold, but the standard gives guarantees about program behavior, never about underlying machine code or optimizations.

E.g. here is MSVC (in non-optimizing mode) not inlining or constant-folding a constexpr function: https://gcc.godbolt.org/z/dETGo44cf — you need to turn on optimizations to get it to just return 7.
dangi12012
Posts: 1062
Joined: Tue Apr 28, 2020 10:03 pm
Full name: Daniel Infuehr

Re: constexpr question

Post by dangi12012 »

Don't mix concepts:

consteval: Function garuanteed to be evaluated at compiletime
constexpr var: initialized at compiletime
constexpr function: may or may not be evaluated at compiletime depending on the context

const: initialized at runtime. Immutable
static: depends heavily on the context. Means constant address for most things.

You can take the address of a constexpr int.
You cannot take the address of a consteval function or a preprocessor macro.

Then you have also have fun like const class with mutable members...
Worlds-fastest-Bitboard-Chess-Movegenerator
Daniel Inführ - Software Developer
Rein Halbersma
Posts: 749
Joined: Tue May 22, 2007 11:13 am

Re: constexpr question

Post by Rein Halbersma »

Sesse wrote: Wed Sep 14, 2022 12:11 am constexpr has no guarantee of constant-time evaluation. It merely guarantees that the value can be used in certain contexts such as template parameters and array sizes, but that doesn't mean that it has to constant-fold if it doesn't care to. consteval and constinit add yet more guarantees on top of this (for instance, anything constinit will be initialized before any code is run), but you still don't have a guarantee of constant folding. Heck, C++ doesn't even guarantee that “int x = 24;” won't be represented in assembler as a function computing the factorial of 4, recursively (and on certain platforms, larger constants will indeed need to be pieced together by combining multiple loads; are those constant or not? :-) ). For many things, the easiest way to get the behavior the standard mandates is indeed to constant-fold, but the standard gives guarantees about program behavior, never about underlying machine code or optimizations.

E.g. here is MSVC (in non-optimizing mode) not inlining or constant-folding a constexpr function: https://gcc.godbolt.org/z/dETGo44cf — you need to turn on optimizations to get it to just return 7.
But this wasn't the OP's question. Simply initializing a constexpr variable inside foo() and then returning that, will simplify the program at -O0 already. https://gcc.godbolt.org/z/d8YzshsTW
dangi12012
Posts: 1062
Joined: Tue Apr 28, 2020 10:03 pm
Full name: Daniel Infuehr

Re: constexpr question

Post by dangi12012 »

Rein Halbersma wrote: Wed Sep 14, 2022 10:39 pm
Sesse wrote: Wed Sep 14, 2022 12:11 am constexpr has no guarantee of constant-time evaluation. It merely guarantees that the value can be used in certain contexts such as template parameters and array sizes, but that doesn't mean that it has to constant-fold if it doesn't care to. consteval and constinit add yet more guarantees on top of this (for instance, anything constinit will be initialized before any code is run), but you still don't have a guarantee of constant folding. Heck, C++ doesn't even guarantee that “int x = 24;” won't be represented in assembler as a function computing the factorial of 4, recursively (and on certain platforms, larger constants will indeed need to be pieced together by combining multiple loads; are those constant or not? :-) ). For many things, the easiest way to get the behavior the standard mandates is indeed to constant-fold, but the standard gives guarantees about program behavior, never about underlying machine code or optimizations.

E.g. here is MSVC (in non-optimizing mode) not inlining or constant-folding a constexpr function: https://gcc.godbolt.org/z/dETGo44cf — you need to turn on optimizations to get it to just return 7.
But this wasn't the OP's question. Simply initializing a constexpr variable inside foo() and then returning that, will simplify the program at -O0 already. https://gcc.godbolt.org/z/d8YzshsTW
Is it 100% safe to assume the c++ compiler will never OR those 3 bitboards together every time it executes that code, but instead inserts a 64bit constant that is the result of (Rank6 | Rank7 | Rank8) to avoid calculate it every time?

Clang GCC MSVC - 100% Yes!
Some embedded micropocessor C++ Compiler: Not 100% safe
You can FORCE the issue by using template parameters or macros.

So for all intents and purposes the compiler knows constexpr is immutable and will constant fold it away. 100% yes for clang GCC msvc
Worlds-fastest-Bitboard-Chess-Movegenerator
Daniel Inführ - Software Developer