C++11 constexpr: computing exp at compile time

While trying out constexpr I was wondering what else can I get compiler to compute. I can pretty much do any integral computation with both C++ Template Metaprogramming and constexpr, but can I do some floating point computations? It appears I can!!

So I set out to compute exp(z), using Taylor series implementation for exp:

//  can't deal with negative powers!!
//
constexpr double pow(double x, int y)
{
    return y == 0 ? 1.0 : x * pow(x, y-1);
}

constexpr int factorial(int x)
{
    return x == 0 ? 1 : x * factorial(x-1);
}

constexpr double exp(double x)
{
    return 1.0 + x + pow(x,2)/factorial(2) + pow(x, 3)/factorial(3)
        + pow(x, 4)/factorial(4) + pow(x, 5)/factorial(5)
        + pow(x,6)/factorial(6) + pow(x, 7)/factorial(7)
        + pow(x, 8)/factorial(8) + pow(x, 9)/factorial(9);
}

int main()
{
    constexpr double exp1 = exp(1.0);
    return 0;
}

In here I have pow function to compute powers of a floating point number, mine is bit limited as it can do integral powers only; factorial straight forward. exp function function goes to 10 component evaluation. With exp(1) I do see value of 2.7182815255731922 in debug window while cout¬†prints it to be 2.71828. Since this is floating point value I can’t do static_assert.

Now what’s the proof that it was all compile time rather than runtime evaluation, the only way to confirm is to look at what is executed by processor, that’s right assembly:

cpp-cxpr`main at main.cpp:388:
0x100000cc0:  pushq  %rbp
0x100000cc1:  movq   %rsp, %rbp
0x100000cc4:  movl   $0x0, %eax
0x100000cc9:  movsd  0x247(%rip), %xmm0
0x100000cd1:  movl   $0x0, -0x4(%rbp)
0x100000cd8:  movsd  %xmm0, -0x10(%rbp)
0x100000cdd:  popq   %rbp
0x100000cde:  ret

Note: there is no instruction for callq, which means no function was called. Since exp(1.0) was evaluated at compile time it’s value was loaded in to register %xmm0 at line 5 and then loaded into a local variable (negative offset from %rbp register) at line 7. Life is a bit easy working with debug builds.

Had we not evaluated exp(z) on compile time, just remove constexpr from code in main:

double exp1 = exp(1); 
// no constexpr - this will be evaluated at runtime

generated assembly for main function is:

cpp-cxpr`main at main.cpp:388:
0x1000009e0:  pushq  %rbp
0x1000009e1:  movq   %rsp, %rbp
0x1000009e4:  subq   $0x10, %rsp
0x1000009e8:  movabsq $0x1, %rax
0x1000009f2:  cvtsi2sdq %rax, %xmm0
0x1000009f7:  movl   $0x0, -0x4(%rbp)
0x1000009fe:  callq  0x100000b10        ; exp(double) at main.cpp:354
0x100000a03:  movl   $0x0, %eax
0x100000a08:  movsd  %xmm0, -0x10(%rbp)
0x100000a0d:  addq   $0x10, %rsp
0x100000a11:  popq   %rbp
0x100000a12:  ret

Here we are loading abs value 1 into register %rax (line 5), then converting int value to floating point with cvtsi2sdq instruction at line 6 and writing that in %xmm0 register. Then at line 8 we are calling exp(double) function which will again put the value in register %xmm0. This value is then moved to a local variable at line 10 (negative offset from %rbp register).

The value computed at runtime is : 2.7182815255731922

Here is the assembly code generated for exp(double) function:

cpp-cxpr`exp(double) at main.cpp:354:
0x100000b10:  pushq  %rbp
0x100000b11:  movq   %rsp, %rbp
0x100000b14:  subq   $0x90, %rsp
0x100000b1b:  movl   $0x2, %edi
0x100000b20:  movabsq $0x1, %rax
0x100000b2a:  cvtsi2sdq %rax, %xmm1
0x100000b2f:  movsd  %xmm0, -0x8(%rbp)
0x100000b34:  addsd  -0x8(%rbp), %xmm1
0x100000b39:  movsd  -0x8(%rbp), %xmm0
0x100000b3e:  movsd  %xmm1, -0x10(%rbp)
0x100000b43:  callq  0x100000d20           ; pow(double, int) at main.cpp:344
0x100000b48:  movl   $0x2, %edi
0x100000b4d:  movsd  %xmm0, -0x18(%rbp)
0x100000b52:  callq  0x100000d90           ; factorial(int) at main.cpp:349
0x100000b57:  movl   $0x3, %edi
...
...
; many more instructions to make 7 more calls to pow and factorial
...
...
0x100000d17:  addq   $0x90, %rsp
0x100000d1e:  popq   %rbp
0x100000d1f:  ret    

;
;  similarly there is code for pow and factorial
;

That’s all I have for this post..

Till next time.