Skip to content

A Deep research of constexpr by .S file and GDB

The Test File

constexpr int square(int x) { return x*x; }

int main() {
    constexpr int a = square(5);  // 25 is calc at compile time
    int y = 10;
    int b = square(y);            // at runtime
}

Are They Really Computed At the Time They Should Be?

Lets see if above is right.

$ g++ -O2 -std=c++17 -S constexpr.cpp -o constexpr.S
$ cat ./constexpr.S | grep 25
        movl    $25, %esi

So it is clear that there is a literal put in %esi, where %esi is

what is %esi used for?

ChatGPT said:

Ah, %esi is one of the general-purpose registers in x86-64 assembly.

We do have a variable/argument equals to 25, which cannot be others except a, while for b, it should not be calculated...

$ cat ./constexpr.S | grep 100
        movl    $100, %esi

Oops, we have literal 100 at compile time. Why?

$ g++ -O0 -std=c++17 -S constexpr.cpp -o constexpr.S
$ cat ./constexpr.S | grep 25
        movl    $25, -12(%rbp)
        movl    $25, %esi
$ cat ./constexpr.S | grep 100
$

That reason is: the -O0 optimisation does constant folding, while -O2 does constant folding and constant propagation.
- constant folding: if we know a value of constant, just write the result.
- constant propagation: if we know a value of a var by another var with constant value, we write it in. - Actually -O0 parially does this as well

So if we use -O0, we can see why there constexpr can be calc at runtime if the param is not constant, and it should not be propagated

$ cat << EOF > const-op.cpp
int main() {
    int x = 3*3;
    int y = x + 1;
    return x;
}
EOF
$ g++ -O0 -S const-op.cpp -o const-op.S
$ cat const-op.S | grep 10
$ g++ -O2 -S const-op.cpp -o const-op.S
$ cat const-op.S | grep 10
        movl    $10, %eax
$ 

Back to our example, we should not see literal 100 as it is not computed at compile time.

$ g++ -O2 -std=c++17 -S constexpr.cpp -o constexpr.S
$ cat ./constexpr.S | grep 25
        movl    $25, %esi
$ cat ./constexpr.S | grep 100
        movl    $100, %esi
$ g++ -O0 -std=c++17 -S constexpr.cpp -o constexpr.S
$ cat ./constexpr.S | grep 25
        movl    $25, -12(%rbp)
        movl    $25, %esi
$ cat ./constexpr.S | grep 100
$

That's it!

Can We Use GDB?

However, reading from .S file are annoying, that's why we have gdb

...
Breakpoint 1, main () at constexpr.cpp:6
6           constexpr int a = square(5);  // 编译期算出 25
(gdb) p a
$1 = 0
(gdb) p b
$2 = 32767
(gdb) s
7           int y = 10;
(gdb) p a
$3 = 25
(gdb) p b
$4 = 32767
(gdb) s
8           int b = square(y);            // 运行期算
(gdb) s
square (x=10) at constexpr.cpp:3
3       constexpr int square(int x) { return x*x; }
(gdb) p a
$5 = {i = {0, 1045149306}, x = 1.2904777690891933e-08, d = 1.2904777690891933e-08}
(gdb) p b
$6 = {i = {0, 1068498944}, x = 0.0625, d = 0.0625}
(gdb) 

We can see that a is set by literal 25 directly, while we must go into the squre() function to compute the outcome for b!

However, why a is init as 0?

  • The local var should not be set as 0, and there seems no corresponding process in .S...