Showing posts with label GCC. Show all posts
Showing posts with label GCC. Show all posts

Sunday, April 28, 2019

How LLVM optimizes power sums

LLVM optimizes power sums, such as
int sum(int count)
{
  int result = 0;

  for (int j = 0; j < count; ++j)
    result += j*j;

  return result;
}
to code that calculates the result without a loop (godbolt)
sum(int):
        test    edi, edi
        jle     .LBB0_1
        lea     eax, [rdi - 1]
        lea     ecx, [rdi - 2]
        imul    rcx, rax
        lea     eax, [rdi - 3]
        imul    rax, rcx
        shr     rax
        imul    eax, eax, 1431655766
        add     eax, edi
        shr     rcx
        lea     ecx, [rcx + 2*rcx]
        lea     eax, [rax + rcx]
        add     eax, -1
        ret
.LBB0_1:
        xor     eax, eax
        ret
It handles more complex cases too (godbolt) – that is, the optimization is not just a simple pattern matcher. This post will show how the optimization is done.

Loop analysis – scalar evolution

There are many cases where compilers need to track how values are updated within a loop. For example, the loop vectorizer needs to check that the pointers are moved to the adjacent element in the next iteration, and check that no other pointer indexing may alias the range we are vectorizing.

Both GCC and LLVM does this in the same way in their scalar evolution passes, where each variable at iteration \(i\) (we start enumerating iterations from \(0\)) is represented as a function \(f_0(i)\) defined as a linear recurrence of the form
\[f_j(i) =
\begin{cases}
\phi_j & \text{if $i = 0$} \\
f_j(i-1) \odot_{j+1} f_{j+1}(i-1) & \text{if $i > 0$}
\end{cases}\] where \(\odot\in\{+,*\}\).

Example 1

The simplest case is a loop such as
void foo(int m, int *p)
{
  for (int j = 0; j < m; j++)
    *p++ = j;
}
The loop writes \(0\) to *p++ in the first iteration, \(1\) in the second, etc. So we can express the value written at iteration \(i\) as\[f(i) =
\begin{cases}
0 & \text{if $i = 0$} \\
f(i-1) + 1 & \text{if $i > 0$}
\end{cases}\]

Example 2

Polynomials in the iteration variable can also be expressed in this form.
void foo(int m, int k, int *p)
{
  for (int j = 0; < m; j++)
    *p++ = j*j*j - 2*j*j + k*j + 7;
}
We will see below how to build the functions, but the result for the value stored in this loop is \[\begin{align}f_2(i) & =
\begin{cases}
2\phantom{f_0(i-1) + f_1(i-1)} & \text{if $i = 0$} \\
f_2(i-1) + 6 & \text{if $i > 0$}
\end{cases}\\
f_1(i) & =
\begin{cases}
k-1 & \text{if $i = 0$} \\
f_1(i-1) + f_2(i-1)\phantom{2} & \text{if $i > 0$}
\end{cases}\\
f(i) = f_0(i) & =
\begin{cases}
7 & \text{if $i = 0$} \\
f_0(i-1) + f_1(i-1)\phantom{2} & \text{if $i > 0$}
\end{cases}\end{align}\] One optimization we can see directly from these functions is that the value can be calculated by just three additions within the loop
void foo(int m, int k, int *p)
{
  int t0 = 7;
  int t1 = k-1;
  int t2 = 2;
  for (int j = 0; j < m; j++) {
    *p++ = t0;
    t0 = t0 + t1;
    t1 = t1 + t2;
    t2 = t2 + 6;
  }
}
which is a useful optimization for architectures were multiplication is expensive. This kind of code is, however, uncommon, so most compilers do not do this optimzation – but they usually do this for simpler cases, such as
void foo(int m, int k, int *p)
{
  for (int j = 0; < m; j++)
    *p++ = k*j + 7;
}
as constructs of the form k*j+7 are common in address calculations.

Chains of recurrences

It is cumbersome to write the recursive functions all the time, so the functions are usually written in the form \(\{\phi_j, \odot_{j+1}, f_{j+1}\}\). For example \[\begin{align}f_2(i) & =
\begin{cases}
2\phantom{f_0(i-1) + f_1(i-1)} & \text{if $i = 0$} \\
f_2(i-1) + 6 & \text{if $i > 0$}
\end{cases} \phantom{xx}\text{is written as $\{2,+,6\}$}\\
f_1(i) & =
\begin{cases}
k-1 & \text{if $i = 0$} \\
f_1(i-1) + f_2(i-1)\phantom{2} & \text{if $i > 0$}
\end{cases} \phantom{xx}\text{is written as $\{k-1,+,f_2\}$}\\
f(i) = f_0(i) & =
\begin{cases}
7 & \text{if $i = 0$} \\
f_0(i-1) + f_1(i-1)\phantom{2} & \text{if $i > 0$}
\end{cases} \phantom{xx}\text{is written as $\{7,+,f_1\}$}\end{align}\] These can be chained, so \(f(i)\) can be written as a chain of recurrences (CR) \(\{7,+,\{k-1,+,\{2,+,6\}\}\}\). The inner curly braces are redundant, so the CR is usually written as a tuple \(\{7,+,k-1,+,2,+,6\}\).

Building the chains of recurrences

The chains of recurrences are built by iterating over the code and calculating the CR for the result of each operation (or marking it as unknown if we cannot handle the operation), using simplification rules such as\[\begin{align}c * \{\phi_0, +, \phi_1\} & \phantom{xx} \Rightarrow \phantom{xx} \{c * \phi_0, +, c * \phi_1\} \\
\{\phi_0, +, \phi_1\} + \{\psi_0, +, \psi_1\} & \phantom{xx} \Rightarrow \phantom{xx} \{\phi_0 + \psi_0, +, \phi_1 + \psi_1\} \\
\{\phi_0, +, \phi_1\}* \{\psi_0, +, \psi_1\} & \phantom{xx} \Rightarrow \phantom{xx} \{\phi_0 * \psi_0, +, \psi_1 * \{\phi_0, +, \phi_1\} + \phi_1 * \{\psi_0, +, \psi_1\} + \phi_1*\psi_1\} \\
\{\phi_0, +, \phi_1,+,0\} & \phantom{xx} \Rightarrow \phantom{xx} \{\phi_0, +, \phi_1\}\end{align}\] So for the loop from the sum function
for (int j = 0; j < count; ++j)
  result += j*j;
we start with j which we know has the CR \(\{0,+,1\}\) per Example 1. This is then used as j*j when calculating result, so we calculate the CR for j*j using the simplification rules as \[\begin{align}j*j& = \{0,+,1\} * \{0,+,1\} \\
& = \{0 * 0, +, 1 * \{0, +,1\} + 1 * \{0, +, 1\} + 1*1\} \\
& = \{0, +, 1,+,2\}\end{align}\] Similar calculations for result gives us the CR \(\{0,+,0,+,1,+,2\}\) for the value at the beginning of the iteration, and \(\{0,+,1,+,3,+,2\}\) after adding j*j.

Doing the optimization

The optimization is done during induction variable simplification, and LLVM has transformed the function to a form more convenient for analysis and optimization
int sum(int count)
{
  int result = 0;

  if (count > 0) {
    int j = 0;
    do {
      result = result + j*j;
      ++j;
    } while (j < count);
  }

  return result;
}
or as it looks like in the LLVM IR
define i32 @sum(i32) {
  %2 = icmp sgt i32 %0, 0
  br i1 %2, label %3, label %6

; <label>:3:
  br label %8

; <label>:4:
  %5 = phi i32 [ %12, %8 ]
  br label %6

; <label>:6:
  %7 = phi i32 [ 0, %1 ], [ %5, %4 ]
  ret i32 %7

; <label>:8:
  %9 = phi i32 [ %13, %8 ], [ 0, %3 ]     ; {0,+,1}
  %10 = phi i32 [ %12, %8 ], [ 0, %3 ]    ; {0,+,0,+,1,+,2}
  %11 = mul nsw i32 %9, %9                ; {0,+,1,+,2}
  %12 = add nuw nsw i32 %11, %10          ; {0,+,1,+,3,+,2}
  %13 = add nuw nsw i32 %9, 1             ; {1,+,1}
  %14 = icmp slt i32 %13, %0
  br i1 %14, label %8, label %4
}
The compiler can see that the function returns 0 if count <= 0, otherwise it returns the value of result at loop iteration count-1.

One nice property of the chains of recurrences is that it is easy to calculate the value at a specific iteration – if we have a CR \(\{\phi_0,+,\phi_1,+,\ldots,+,\phi_n\}\), then the value at iteration \(i\) can be calculated as \[\begin{align}f(i) & = \sum_{j=0}^{n}\phi_j{i \choose j} \\ & = \phi_0 + \phi_1i + \phi_2{i(i-1)\over 2!} + \ldots + \phi_n{i(i-1)\cdots(i-n+1)\over n!}\end{align}\] Inserting the values for the CR \(\{0,+,1,+,3,+,2\}\) describing result gives us \[f(i) = i + {3i(i-1)\over 2} + {i(i-1)(i-2) \over 3}\] The compiler now only need to insert code that calculates this with \(i =\) count-1, after the loop
result = count-1 + 3*(count-1)*(count-2)/2 + (count-1)*(count-2)(count-3)/3;
but it need to take some care to calculate in the correct precision (as temporary values may not fit in 32-bit integers). Integer division is slow, so it is also doing tricks to replace the divisions with multiplication and shift instructions. The result is the LLVM IR
  %4 = add i32 %0, -1
  %5 = zext i32 %4 to i33
  %6 = add i32 %0, -2
  %7 = zext i32 %6 to i33
  %8 = mul i33 %5, %7
  %9 = add i32 %0, -3
  %10 = zext i32 %9 to i33
  %11 = mul i33 %8, %10
  %12 = lshr i33 %11, 1
  %13 = trunc i33 %12 to i32
  %14 = mul i32 %13, 1431655766
  %15 = add i32 %14, %0
  %16 = lshr i33 %8, 1
  %17 = trunc i33 %16 to i32
  %18 = mul i32 %17, 3
  %19 = add i32 %15, %18
  %20 = add i32 %19, -1
Inserting this makes the loop become dead, so it is later removed by dead code elimination, and we eventually end up with the code
sum(int):
        test    edi, edi
        jle     .LBB0_1
        lea     eax, [rdi - 1]
        lea     ecx, [rdi - 2]
        imul    rcx, rax
        lea     eax, [rdi - 3]
        imul    rax, rcx
        shr     rax
        imul    eax, eax, 1431655766
        add     eax, edi
        shr     rcx
        lea     ecx, [rcx + 2*rcx]
        lea     eax, [rax + rcx]
        add     eax, -1
        ret
.LBB0_1:
        xor     eax, eax
        ret

Performance

This optimization is not always profitable. For example,
int sum(int count)
{
  int result = 0;

  for (int j = 0; j < count; ++j)
    result += j*j*j*j*j*j;

  return result;
}
is calculated by three 32-bit multiplications and one addition within the loop, while the optimized version needs six 64-bit multiplications, five 32-bit multiplications, and a slew of other instructions (godbolt), so the optimized version is slower for small values of count. I benchmarked on my PC, and count must be larger than 5 for the optimized version to be faster than the loop. Smaller CPUs with, for example, more expensive 64-bit multiplication, will need a higher count for the optimization to help. And CPUs not having instructions for 64-bit multiplication (godbolt) will need a much higher count.

One problem with this optimization is that it is hard for developers to make the compiler generate a loop for this if they know that the majority of values used in reality are small enough for the loop to be the fastest option. GCC does, therefore, not replace the final value of a loop if the expression is expensive
/* Do not emit expensive expressions.  The rationale is that
   when someone writes a code like

   while (n > 45) n -= 45;

   he probably knows that n is not large, and does not want it
   to be turned into n %= 45.  */
|| expression_expensive_p (def))
So GCC not doing this optimization is a feature – not a bug.

Further readings

Chains of recurrences:
Loop optimizations using chains of recurrences:
Optimizing division using multiplication and shift instructions:

Updated: The original post incorrectly called the power sums “geometric sums”.

Saturday, January 26, 2019

Building GCC 1.27

GCC 1.27 was released in 1988 and is the first version of GCC supporting the x86 CPU. I thought it would be fun to make it work on my desktop computer.

Mikhail Maltsev wrote a great blog post about this a while back “Building and using a 29-year-old compiler on a modern system”. I used Mikhail’s work as a starting point, but I built on a 64-bit Ubuntu system so I needed to update paths and as/ld options for running on a 64-bit OS, and I had much bigger problems with the ancient GCC not understanding the system headers. I also enabled the DBX debug format instead of the UNIX/32V SDB format that GDB does not understand. But I did not need to make that big changes to Mikhail’s patch.

It is amazing how well things work – the modern assembler, linker, and debugger handles the code generated by GCC 1.27 without any problems. And command options such as -O, -E, -S, -c, -g, -W, -pedantic, and -fomit-frame-pointer does what you expect from using a modern GCC. All the options are documented in the manual page – you can format the text by passing the gcc.1 file to man as
man -l gcc.1

How to build GCC 1.27 on Ubuntu

I have built the compiler on 64-bit Ubuntu Desktop 16.04 as described below.

Prerequisites

GCC 1.27 is a 32-bit program, so we need to install 32-bit compiler and runtime support
sudo apt install gcc-multilib

Downloading and preparing the source code

Download the source code and the patch, and apply the patch as
wget https://gcc.gnu.org/pub/gcc/old-releases/gcc-1/gcc-1.27.tar.bz2
wget https://gist.github.com/kristerw/b854b6d285e678452a44a6bcbf7ef86f/raw/gcc-1.27.patch
tar xf gcc-1.27.tar.bz2 
cd gcc-1.27
patch -p1 < ../gcc-1.27.patch

Configuring the source code

The compiler is configured by setting up symbolic links to the correct configuration files
ln -s config-i386v.h config.h
ln -s tm-i386v.h tm.h
ln -s i386.md md
ln -s output-i386.c aux-output.c
You may want to change where the compiler is installed by updating bindir and libdir in the Makefile. I set them to
bindir = /home/kristerw/compilers/gcc-1.27/bin
libdir = /home/kristerw/compilers/gcc-1.27/lib

Build and install

The compiler is built in two (or three) stages, We start by building it using the system compiler
make
We then build it again using the newly built compiler
make stage1
make CC=stage1/gcc CFLAGS="-O -Bstage1/ -Iinclude"
As a third, optional, step, we build it again using the second compiler and checks that the resulting binaries are identical with the second compiler (if not, then the compiler has miscompiled itself).
make stage2
make CC=stage2/gcc CFLAGS="-O -Bstage2/ -Iinclude"
diff cpp stage2/cpp
diff gcc stage2/gcc
diff cc1 stage2/cc1
We are now ready to install the compiler
make install

Saturday, August 11, 2018

Inlining and microbenchmarking

Inlining is important for C++ performance, but the compiler must be careful not to increase the code size too much. GCC’s inlining process is basically inlining functions sorted by priority until a growth percentage limit is hit (or until all relevant functions have been inlined), which works fine for large translation units where the parts of the code not helped by inlining can compensate for the code increase in the parts where inlining helps. But it works less well for small translating units that need much inlining, which is common when doing microbenchmarks.

Take for example this quick-bench benchmark from Bartlomiej Filipek’s blog post “Speeding Up string_view String Split Implementation” that measures the performance for different ways of splitting a string.

We can see that StringSplitStd is about 9% faster than StringViewSplit (they have the score 7829 and 7189), but the reason for this difference is that GCC has inlined everything for StringSplitStd, but not for StringViewSplit.

We get a different result if we run the benchmark with a different set of functions. For example, removing StringViewSplitStd and StringViewSplitPtr from the benchmark makes the compiler make different inlining decisions, and we get the same performance for both StringSplitStd and StringViewSplit (quick-bench).

It is a good idea when doing microbenchmarking to check that the compiler makes the same inlining decisions as when the code is used in a real use case (in a realistically sized translation unit).

Saturday, July 28, 2018

Don’t trust quick-bench results you see on the internet

It is easy to use quick-bench to benchmark small C++ functions, but it is hard to ensure they are measuring what is intended. Take this benchmark as an example. It is measuring different ways of transforming a string to lower case using code of the form
void by_index(char* s, size_t n)
{
    for (size_t i = 0; i < n; ++i)
        s[i] = tolower(s[i]);
}

static void LowerByIndex(benchmark::State& state)
{
    // Code inside this loop is measured repeatedly
    for (auto _ : state) {
        char upper_string[] = "UPPER TEXT";
        by_index(upper_string, 10);

        // Make sure the variable is not optimized away by compiler
        benchmark::DoNotOptimize(upper_string);
    }
}

// Register the function as a benchmark
BENCHMARK(LowerByIndex);
We are going to look at how compilers may optimize it in ways that were probably not intended.

How a compiler may optimize the code

The following corresponds to how GCC optimizes this on Linux when using the libc++ header files.1

The tolower function is defined in /user/include/ctype.h as
extern const __int32_t **__ctype_tolower_loc (void)
    throw () __attribute__ ((__const__));

extern __inline __attribute__ ((__gnu_inline__)) int
tolower (int __c) throw ()
{
    return __c >= -128 && __c < 256 ? (*__ctype_tolower_loc())[__c] : __c;
}
This is inlined into by_index, which in turn is inlined into LowerByIndex
static void LowerByIndex(benchmark::State&amp; state)
{
    // Code inside this loop is measured repeatedly
    for (auto _ : state) {
        char upper_string[] = "UPPER TEXT";
        for (size_t i = 0; i < 10; ++i) {
            int __c = upper_string[i]);
            upper_string[i] = __c >= -128 && __c < 256 ? (*__ctype_tolower_loc())[__c] : __c;
        }

        benchmark::DoNotOptimize(upper_string);
    }
}

A char has values in the range -128 to 127 when compiling for the x86 architecture, so the compiler determines that the comparisons in
upper_string[i] = __c >= -128 && __c < 256 ? (*__ctype_tolower_loc())[__c] : __c;
are always true (as __c is assigned from a char), and the line is simplified to
upper_string[i] = (*__ctype_tolower_loc())[__c];

The __ctype_tolower_loc function is decorated with the const attribute so the function call can be moved out of the loop, provided the loop body is always executed. Compilers typically represent for-loops as an if-statement followed by a do-while loop – for example
for (i = 0; i < n; ++i) {
    do_something();
}
is represented as
i = 0;
if (i < n) {
    do {
        do_something();
        ++i;
    } while (i < n);
}
This representation simplifies the work for the compiler as the pre-condition is separated from the actual loop, and constant expressions can now trivially be moved out of the loop. The if-statement is simplified to always true (or always false) when the iteration count is known at compile time. Rewriting the loops, and moving __ctype_tolower_loc out of the loops gives us the result
static void LowerByIndex(benchmark::State&amp; state)
{
    auto it = state.begin()
    if (it != state.end()) {
        int *p = *__ctype_tolower_loc();

        // Code inside this loop is measured repeatedly
        do {
            char upper_string[] = "UPPER TEXT";
            size_t i = 0;
            do {
                int __c = upper_string[i]);
                upper_string[i] = p[__c];
                ++i;
            } while (i < 10);

            benchmark::DoNotOptimize(upper_string);

            ++it;
        } while (it != state.end());
    }
}
Note that the call to __ctype_tolower_loc is now outside of the code segment being measured!

The inner loop is small, so it is fully unrolled
static void LowerByIndex(benchmark::State&amp;amp; state) {
    auto it = state.begin()
    if (it != state.end()) {
        int *p = *__ctype_tolower_loc();

        // Code inside this loop is measured repeatedly
        do {
            char upper_string[] = "UPPER TEXT";
            int __c0 = upper_string[0]);
            upper_string[0] = p[__c0];
            int __c1 = upper_string[1]);
            upper_string[1] = p[__c1];
            int __c2 = upper_string[2]);
            upper_string[2] = p[__c2];
            int __c3 = upper_string[3]);
            upper_string[3] = p[__c3];
            int __c4 = upper_string[4]);
            upper_string[4] = p[__c4];
            int __c5 = upper_string[5]);
            upper_string[5] = p[__c5];
            int __c6 = upper_string[6]);
            upper_string[6] = p[__c6];
            int __c7 = upper_string[7]);
            upper_string[7] = p[__c7];
            int __c8 = upper_string[8]);
            upper_string[8] = p[__c8];
            int __c9 = upper_string[9]);
            upper_string[9] = p[__c9];

            benchmark::DoNotOptimize(upper_string);

            ++it;
        } while (it != state.end());
    }
}
All accesses to upper_string are now to known positions, so the compiler can easily forward the values written to where they are read, and the generated code does not need to initialize or read from upper_string
static void LowerByIndex(benchmark::State&amp;amp; state) {
    auto it = state.begin()
    if (it != state.end()) {
        int *p = *__ctype_tolower_loc();

        // Code inside this loop is measured repeatedly
        do {
            char upper_string[10];
            upper_string[0] = p['U'];
            upper_string[1] = p['P'];
            upper_string[2] = p['P'];
            upper_string[3] = p['E'];
            upper_string[4] = p['R'];
            upper_string[5] = p[' '];
            upper_string[6] = p['T'];
            upper_string[7] = p['E'];
            upper_string[8] = p['X'];
            upper_string[9] = p['T'];

            benchmark::DoNotOptimize(upper_string);

            ++it;
        } while (it != state.end());
    }
}
Finally, several characters are the same, so the compiler can CSE (Common Subexpression Elimination) them to only load these values once from p
static void LowerByIndex(benchmark::State&amp;amp; state)
{
    auto it = state.begin()
    if (it != state.end()) {
        int *p = *__ctype_tolower_loc();

        // Code inside this loop is measured repeatedly
        do {
            char upper_string[10];
            upper_string[0] = p['U'];
            upper_string[1] = upper_string[2] = p['P'];
            upper_string[3] = upper_string[7] = p['E'];
            upper_string[4] = p['R'];
            upper_string[5] = p[' '];
            upper_string[6] = upper_string[9] = p['T'];
            upper_string[8] = p['X'];

            benchmark::DoNotOptimize(upper_string);

            ++it;
        } while (it != state.end());
    }
}
That is, the compiler has used its knowledge of the input to basically hard code the result (this may be OK, depending on what the benchmark is trying to measure). And it has moved some code out of the code segment being measured (which is probably not OK for the benchmark).

How to fix the benchmark

I usually prefer keeping the benchmarked function in a separate translation unit in order to guarantee that the compiler cannot take advantage of the code setting up the benchmark, but that does not work in quick-bench. One way to get a similar effect is to mark the function as noinline, but that only solves part of the problem – compilers do various interprocedural optimizations, and for GCC you should specify at least noclone too. Other compilers may need to be restricted in different ways.

It may also be possible to hide information from the compiler by using volatile or functionality from the benchmarking framework (such as benchmark::DoNotOptimize and benchmark::ClobberMemory), but this may also introduce unintended behavior. For example, these workarounds make the code look “unusual” to the compiler, which may make various heuristics make different optimization decisions compare to normal usage.

In general, you need to spend some time analyzing the benchmark in order to determine what the result means (for example, are we measuring the difference in how fast different methods can transform a string, or are we only measuring the difference for the string “UPPER TEXT”?), or as Fabian Giesen says in “A whirlwind introduction to dataflow graphs
With microbenchmarks, like a trial lawyer during cross-examination, you should never ask a question you don’t know the answer to (or at least have a pretty good idea of what it is). Real-world systems are generally too complex and intertwined to understand from surface measurements alone. If you have no idea how a system works at all, you don’t know what the right questions are, nor how to ask them, and any answers you get will be opaque at best, if not outright garbage. Microbenchmarks are a useful tool to confirm that an existing model is a good approximation to reality, but not very helpful in building these models to begin with.


1. The libstdc++ headers use a normal function call for tolower instead of using the inlined version from ctype.h. You can see the optimization from this blog post using libstdc++ too by including ctype.h before any C++ header (but this is not possible in quick-bench, as it adds its own headers before the user code).

Monday, June 25, 2018

Useful GCC address sanitizer checks not enabled by default

Some useful address sanitizer checks are disabled by default because they are relatively expensive (or, as for the std::vector checking, need to be enabled for all translation units).

Use after return

The address sanitizer warns when a variable is used after it has gone out of scope in a function, but it does not warn when the variable is used after the function return. That can, however, be enabled by adding detect_stack_use_after_return=1 to the ASAN_OPTIONS environment variable.

Example

int *ptr;

__attribute__((noinline))
void foo(void)
{
  int a;
  ptr = &a;
}

int main(void)
{
  foo();
  return *ptr;  // Error
}
Compile as
gcc -O -fsanitize=address file.c
and add detect_stack_use_after_return=1 to the ASAN_OPTIONS environment variable before running the program
env ASAN_OPTIONS="detect_stack_use_after_return=1" ./a.out

Pointer comparison

It is not valid to compare two pointers from different objects using the relational operators <, <=, >, and >=. This can be detected by compiling with -fsanitize=address,pointer-compare and adding detect_invalid_pointer_pairs=1 to the ASAN_OPTIONS environment variable.

Note: -fsanitize=pointer-compare was added in GCC 8.

Example

#include <stdlib.h>

int main(void)
{
  char *p = malloc(42);
  char *q = malloc(42);

  int tmp = p < q;  // Error

  free(p);
  free(q);

  return tmp;
}
Compile as
gcc -fsanitize=address,pointer-compare file.c
and add detect_invalid_pointer_pairs=1 to the ASAN_OPTIONS environment variable before running the program
env ASAN_OPTIONS="detect_invalid_pointer_pairs=1" ./a.out

Pointer subtraction

It is not valid to subtract pointers that point into different objects. This can be detected by compiling with -fsanitize=address,pointer-subtract and adding detect_invalid_pointer_pairs=1 to the ASAN_OPTIONS environment variable.

Note: -fsanitize=pointer-subtract was added in GCC 8.

Example

#include <stdlib.h>

int main(void)
{
  char *p = malloc(42);
  char *q = malloc(42);

  int tmp = p - q;  // Error

  free(p);
  free(q);

  return tmp;
}
Compile as
gcc -O -fsanitize=address,pointer-subtract file.c
and add detect_invalid_pointer_pairs=1 to the ASAN_OPTIONS environment variable before running the program
env ASAN_OPTIONS="detect_invalid_pointer_pairs=1" ./a.out

std::vector checking

The address sanitizer does not detect out-of-bounds accesses to the unused capacity of a vector, such as
std::vector<int> v(2);
int* p = v.data();
v.pop_back();
return p[1];  // Error
because the memory is valid, even though it is an error to use it. It is possible to make the address sanitizer warn for this by compiling with -D_GLIBCXX_SANITIZE_VECTOR which makes libstdc++ annotate the memory so that the validity can be tracked. The annotations must be present on all vector operations or none, so this macro must be defined to the same value for all translation units that create, destroy or modify vectors.

Note: _GLIBCXX_SANITIZE_VECTOR was added in the GCC 8 libstdc++.

Example

#include <vector>

int main()
{
  std::vector<int> v(2);
  int* p = v.data();
  v.pop_back();
  return p[1];  // Error
}
Compile as
g++ -O -fsanitize=address -D_GLIBCXX_SANITIZE_VECTOR file.cpp

Thursday, April 12, 2018

Compilation time – -std=c++98 vs. -std=c++11

I saw on twitter that it takes more than twice as much time compiling the Ogre graphics engine using GCC than when using Clang. My experience is that GCC and Clang usually compile with similar speed, so I decided to look into why compiling Ogre is different.

It turned out that a big part of the difference comes from which C++ version the compilers use per default – clang-5.0 defaults to C++98, while gcc-7 defaults to a newer version. Forcing the compilers to use C++98 by passing -std=c++98 makes GCC compile Ogre in about half the time (668s vs. 1135s), while passing -std=c++11 nearly doubles the time Clang needs to compile it!

One reason for this difference is that some of the standard include files are more expensive in C++11 mode as they suck in more dependencies. For example, compiling a file containing just the line
#include <memory>
takes 0.16 seconds on my computer when using C++11
> time -p g++ -O2 -c test.cpp -std=c++11
real 0.16
user 0.14
sys 0.01
while compiling it as C++98 is faster
> time -p g++ -O2 -c test.cpp -std=c++98
real 0.02
user 0.01
sys 0.01
The 0.14-second difference may not seem that big, but it makes a difference when, as for Ogre, you are compiling more than 500 files, each taking about one second. The increased cost of including standard header files for C++11 compared to C++98 adds about 20% to the Ogre build time.

It is a bit unclear to me exactly where the rest of the slowdown comes from, but it seems to be spread all over the code (I tried to remove various classes in the Ogre code base, and removing 10% of the source code seems to affect both the fast and slow version by about 10%) so I assume this is just because the templates in the C++11 STL are more complex and the compiler needs to work a bit harder each time they are used...

Anyway, the difference in compilation time between -std=c++98 and -std=c++11 was much bigger than I had guessed, and I’ll now ensure I use -std=c++98 when building C++98 code.


Updated: The original blog post said that gcc-7 uses C++11 per default. That was wrong, it defaults to C++14.

Saturday, March 3, 2018

Detecting incorrect C++ STL usage

The GCC and LLVM sanitizers are great for finding problems in C++ code, but they do not detect problems with incorrect STL usage. For example, std::list::merge merges two sorted lists, so the following program is incorrect (as list2 is not sorted)
#include <iostream>
#include <list>

int main()
{
  std::list<int> list1 = {1, 2, 3, 4};
  std::list<int> list2 = {5, 3, 4, 2};
  list1.merge(list2);
  for (auto x : list1)
    std::cout << x << '\n';
}
but this cannot be detected by the sanitizers. The libstdc++ library does, however, have a “debug mode” that can be used to detect this kind of problems. The debug mode is enabled by passing -D_GLIBCXX_DEBUG to the compiler
g++ -O2 -D_GLIBCXX_DEBUG example.cpp
which enables assertions checking the preconditions, and the program fails at runtime
/scratch/gcc-7.2.0/install/include/c++/7.2.0/debug/list:716:
Error: elements in iterator range [__x.begin().base(), __x.end().base())
are not sorted.

Objects involved in the operation:
    iterator "__x.begin().base()" @ 0x0x7fff1754ea10 {
      type = std::__cxx1998::_List_iterator;
    }
    iterator "__x.end().base()" @ 0x0x7fff1754ea40 {
      type = std::__cxx1998::_List_iterator;
    }
Abort (core dumped)

There are a few constructs that are invalid according to the C++ standard but that libstdc++ handles as an extension. One such example is inserting a range of a list into the list the range points to, as in
#include <iostream>
#include <list>

int main()
{
  std::list<int> list1 = {1, 2, 3, 4};
  list1.insert(list1.begin(), list1.begin(), list1.end());
  for (auto x : list1)
    std::cout << x << '\n';
}
The debug mode does not report errors for these extensions when using -D_GLIBCXX_DEBUG, but the debug mode can be made more pedantic by adding -D_GLIBCXX_DEBUG_PEDANTIC
g++ -O2 -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC example.cpp
which reports errors for these too.

One annoyance with the debug mode is that it changes the size of some standard class templates, so you cannot pass containers between translation units compiled with and without debug mode – this often means that you need to build the whole application with debug mode enabled.

The libstdc++ debug mode was introduced in GCC 3.4.

Sunday, February 4, 2018

GCC command options for debugging – -Og and -g3

I listened to a recent CppCast episode where Balázs Török talked about game development and mentioned that C++ abstractions make the code unusable slow in debug builds, and that he is skeptical of debuggability of meta-classes as today's debuggers cannot even handle macros. I think both problems are solvable, and I would argue that GCC is already handling at least the first issue, provided the right command options are used.

Debugging optimized code

GCC can generate debug information when optimizing, so it is possible to run fully optimized code in the debugger. But this is not too useful in reality – many optimizations change the structure of the code, so it is often impossible to single-step in the resulting binary as instructions from different parts of the program are interleaved...

The GCC developers have traditionally tried to limit the damage done by the optimizers for the -O1 optimization level, but -O1 is often used for release builds too, so there is a limit to how much optimizations can be disabled without annoying too many developers – a new optimization level, -Og, were therefore introduced in GCC 4.8. The -Og optimization level enables the optimizations that do not interfere with debugging, and it may even result in a better debugging experience than when compiling without optimizations, as some optimization passes collect information useful for generating better debug information.

The difference between -O1 and -Og is that
  • -Og disables some optimizations, such as if-conversion, that simplifies control flow, so the structure of the generated code is roughly the same as in the source code.
  • -Og disables some passes, such as -ftree-pta and -ftree-sra, that help other optimization passes by propagating information about memory accesses throughout the functions. The effect of this is that those passes now optimize with only the information available locally within each basic block.
  • -Og is less aggressive in the back end peephole optimizations, so each generated instruction is less likely to execute functionality from several statements in the source code.
How much this affects the performance depends a lot on coding style etc., but modern CPUs are great at hiding inefficiencies using branch-prediction, speculative execution, and store to load forwarding, so the difference between -Og and fully optimized code is often surprisingly small, even when using the STL.

Debug information for macros

GCC does not put information about macros in the debug information per default, but it is possible to add it by passing -g3 to the compiler. This makes GDB know of the macros and enables some macro-related commands. But the debugging experience is still not that great. I do not understand why a better support has not been implemented – possibly because inline functions should be used instead of macros...

Sunday, January 21, 2018

GCC back end performance tuning

This is part six of a series “Writing a GCC back end”.

Cost model – TARGET_RTX_COSTS

The compiler often has different options for how it can optimize and emit the code. For example, dividing a 32-bit integer by the constant value 3
i = i / 3;
can be generated as a division instruction, but division instructions are slow, so it may be better to generate this as the equivalent of
i = (((int64_t)i * 0x55555556) >> 32) - (i >> 31);
which gives the same result. GCC decides which to generate by preparing RTL for the different alternatives, and queries the target’s cost function (if implemented) to determine which is the cheapest. Which alternatives are tried depends on which insns are defined in the target description, and what constraints the insns have, but the compiler will, in this case, ask for the costs of subexpressions of the form
(truncate:SI (lshiftrt:DI (mult:DI (sign_extend:DI (reg:SI 88))
                                   (const_int 0x55555556))
                          (const_int 32)))
and compares their combined costs with
(div:SI (reg:SI 88) (const_int 3))

Implementing this cost function is relatively easy for simple architectures – it consists of a switch case for each operation returning the cost expressed as the number of nop instructions  (which usually means the number of cycles)
static bool
machine_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno,
                   int *total, bool speed)
{
  switch (GET_CODE (x))
    {
    case CONST_INT:
      *total = 0;
      return true;

    case AND:
    case IOR:
    case XOR:
      *total = COSTS_N_INSNS (GET_MODE_SIZE (mode) > UNITS_PER_WORD ? 2 : 1);
      return false;

    case ABS:
      *total = COSTS_N_INSNS (FLOAT_MODE_P (mode) ? 1 : 3);
      return false;

    // ...

    default:
      return false;
    }
}

#undef TARGET_RTX_COSTS
#define TARGET_RTX_COSTS machine_rtx_costs
Returning true from cost function means that it has written the cost of the whole RTL expression x to *total, and returning false means just for the first operation in the expression (in which case the cost function will be called separately on the arguments).

The cost function gets complicated fast as the CPU gets more complex with different costs depending on how the operations combine with other operations. For example, an addition may have different cost if it can be done as part of the addressing mode in a memory operation
(set (reg:SI 90)
        (mem:SI (plus:SI (reg:SI 88) (reg:SI 89))))
compared to if it is a normal addition
(set (reg:SI 90)
        (plus:SI (reg:SI 88) (reg:SI 89)))
But the cost function does not need to be too exact – there are many optimizations running after the optimization passes calling the cost function (and it is not obvious what the cost even mean for superscalar out-of-order CPUs anyway...).

Cost model – more configuration options

There are about 30 additional macros guiding performance-related decisions during compilation. These cover various properties such as relative cost of different addressing modes and registers, how expensive branches are compared to arithmetic operations, and when the compiler should unroll/inline memcpy instead of calling the function in the library.

See “Describing Relative Costs of Operations” in “GNU Compiler Collection Internals” for a list of these macros.

Peephole optimizations

The define_peephole2 definition in the target description takes a sequence of insns and transforms them to a new sequence of insns, working in essentially the same way as define_expand. This is used to take advantage of target-specific instructions that the generic peep-hole optimizations cannot do.

But there is in general not much need to write peephole optimizations – the insns describes exactly what they do in the RTL pattern, so GCC can reason about the insns and combine them when possible. So missing peephole optimizations are in general deficiencies in the machine description, such as missing define_insn, too conservative constraints (so that GCC does not believe the transformation is allowed), incorrect cost model (so it seems to be slower), etc.

Tuning optimization passes for the target architecture

GCC lets the user enable or disable optimization passes (using -f-options) and change different thresholds (using --param) when compiling. The default value for all of these can be set by macros in the target-specific configuration file gcc/common/config/machine/machine-common.c.

TARGET_OPTION_OPTIMIZATION_TABLE is used to enable or disable optimization passes at the various optimization levels. For example, this code snippet from the i386 backend enables -free for -O2 and higher optimization levels, and disables -fschedule-insns for all optimization levels
static const struct default_options machine_option_optimization_table[] =
  {
    /* Enable redundant extension instructions removal at -O2 and higher.  */
    { OPT_LEVELS_2_PLUS, OPT_free, NULL, 1 },
    /* Turn off -fschedule-insns by default.  It tends to make the
       problem with not enough registers even worse.  */
    { OPT_LEVELS_ALL, OPT_fschedule_insns, NULL, 0 },

    { OPT_LEVELS_NONE, 0, NULL, 0 }
  };

#undef TARGET_OPTION_OPTIMIZATION_TABLE
#define TARGET_OPTION_OPTIMIZATION_TABLE machine_option_optimization_table

TARGET_OPTION_DEFAULT_PARAMS is used to set the default value for --param parameters. For example, the default value of the parameter l1_cache_line_size is modified as
static void
machine_option_default_params (void)
{
  set_default_param_value (PARAM_L1_CACHE_LINE_SIZE, 16);
}

#undef TARGET_OPTION_DEFAULT_PARAMS
#define TARGET_OPTION_DEFAULT_PARAMS machine_option_default_params

The backend may need to change the default values for other options affecting how the compiler works. For example, it makes sense to make -fno-delete-null-pointer-checks the default for a microcontroller where address 0 is a valid address. This can be done by using TARGET_OPTION_INIT_STRUCT
static void
machine_option_init_struct (struct gcc_options *opts)
{
  opts->x_flag_delete_null_pointer_checks = 0;
}

#undef TARGET_OPTION_INIT_STRUCT
#define TARGET_OPTION_INIT_STRUCT machine_option_init_struct

Further reading

All functionality is described in “GNU Compiler Collection Internals”:

Tuesday, December 12, 2017

More about GCC instruction patterns

This is part five of a series “Writing a GCC back end”.

The previous post about GCC’s low-level IR did only contain the minimum to get started – this post continues with a bit more of the functionality used in machine descriptions.

define_expand

The define_insn definition describes an insn implementing the named functionality (used when converting the higher level IR to RTL), but there are cases where the named functionality must expand to more than one insn, or when the RTL should be generated differently depending on the operands – this is handled by define_expand.

The general format of define_expand is essentially the same as for define_insn – it consists of a name, an RTL template, a condition string containing C++ code to enable/disable the instruction pattern, and a section of C++ code (called preparation statements) that can be used to generate RTL.

Our first example of how to use define_expand is from the Arm handling of rotlsi3 (“rotate left”) – Arm does only have a “rotate right” instruction, but rotating left by x is the same as rotating right by 32-x, and we can generate this as:
(define_expand "rotlsi3"
  [(set (match_operand:SI              0 "s_register_operand" "")
        (rotatert:SI (match_operand:SI 1 "s_register_operand" "")
                     (match_operand:SI 2 "reg_or_int_operand" "")))]
  "TARGET_32BIT"
  {
    if (CONST_INT_P (operands[2]))
      operands[2] = GEN_INT ((32 - INTVAL (operands[2])) % 32);
    else
      {
        rtx reg = gen_reg_rtx (SImode);
        emit_insn (gen_subsi3 (reg, GEN_INT (32), operands[2]));
        operands[2] = reg;
      }
  })
The preparation statements are used to modify the second operand given to rotlsi3 to calculate the correct value for the code generated by the RTL template.

define_expand may choose to skip the generation of the RTL template by executing DONE in the preparation statements. An example of how this is used is the Moxie implementation of mulsidi3 that creates a 64-bit result from multiplying two 32-bit values. The Moxie back end generates this pattern as two instructions (one that calculates the upper 32 bits, and one that calculates the lower 32 bits) where all work is done by the preparation statements
(define_expand "mulsidi3"
  [(set (match_operand:DI 0 "register_operand" "")
        (mult:DI (sign_extend:DI (match_operand:SI 1 "register_operand" "0"))
                 (sign_extend:DI (match_operand:SI 2 "register_operand" "r"))))]
  ""
  {
    rtx hi = gen_reg_rtx (SImode);
    rtx lo = gen_reg_rtx (SImode);

    emit_insn (gen_mulsi3_highpart (hi, operands[1], operands[2]));
    emit_insn (gen_mulsi3 (lo, operands[1], operands[2]));
    emit_move_insn (gen_lowpart (SImode, operands[0]), lo);
    emit_move_insn (gen_highpart (SImode, operands[0]), hi);
    DONE;
  })
Note: This define_expand contains an RTL template, but it is not needed as RTL will not be generated from it. It would have been enough to specify the operands, as in
(define_expand "mulsidi3"
  [(match_operand:DI 0 "register_operand")
   (match_operand:SI 1 "register_operand")
   (match_operand:SI 2 "register_operand")]
  ""
  {
    rtx hi = gen_reg_rtx (SImode);
    rtx lo = gen_reg_rtx (SImode);

    emit_insn (gen_mulsi3_highpart (hi, operands[1], operands[2]));
    emit_insn (gen_mulsi3 (lo, operands[1], operands[2]));
    emit_move_insn (gen_lowpart (SImode, operands[0]), lo);
    emit_move_insn (gen_highpart (SImode, operands[0]), hi);
    DONE;
  })

DONE works as a return statement, and it can be used in conditional code to let the preparation statements override the RTL template for special cases while using the RTL template for the general case. The Arm smaxsi3 instruction pattern (calculating the maximum of two signed integers) uses this to handle two special cases more efficiently:
(define_expand "smaxsi3"
  [(parallel [
    (set (match_operand:SI 0 "s_register_operand" "")
         (smax:SI (match_operand:SI 1 "s_register_operand" "")
                  (match_operand:SI 2 "arm_rhs_operand" "")))
    (clobber (reg:CC CC_REGNUM))])]
  "TARGET_32BIT"
  {
    if (operands[2] == const0_rtx || operands[2] == constm1_rtx)
      {
        /* No need for a clobber of the condition code register here.  */
        emit_insn (gen_rtx_SET (operands[0],
                                gen_rtx_SMAX (SImode, operands[1],
                                              operands[2])));
        DONE;
      }
  })
The problem it solves is that the general case translates to the instructions
cmp   r1, r2
movge r0, r2
movlt r0, r1
which clobbers the condition code, but the special cases max(x,0) and max(x,-1) can be generated as
bic   r0, r1, r1, asr #31
and
orr   r0, r1, r1, asr #31
which do not clobber CC. This optimization could be handled by later optimization passes, but doing it as early as possible (i.e. when generating the RTL) gives more freedom to all later passes to optimize cases that otherwise would have been prevented by the clobbering.

Note: The RTL always have the constant as the second operand for commutative binary operations, so the code does not need to check the first operand.

One other way to return from the preparation statements is to execute FAIL which has the effect of ignoring the instruction pattern in the same way as if the pattern had been disabled by the condition string. An example of this is the AArch64 movmemdi instruction pattern implementing a memory block move:
(define_expand "movmemdi"
  [(match_operand:BLK 0 "memory_operand")
   (match_operand:BLK 1 "memory_operand")
   (match_operand:DI 2 "immediate_operand")
   (match_operand:DI 3 "immediate_operand")]
  "!STRICT_ALIGNMENT"
  {
    if (aarch64_expand_movmem (operands))
      DONE;
    FAIL;
  })
This calls the target-specific aarch64_expand_movmem function that checks if it makes sense to expand the block move inline (that is, if the result will be relatively small) and generates a sequence of move insns if that is the case. If not, it just returns false, which makes this pattern call FAIL, and GCC will ignore this instruction pattern and generate a call to memcpy instead.

The unspec and unspec_volatile expression codes

The RTL template in define_insn contains expressions describing the functionality of the instruction pattern, which enables the optimizers to reason about the insn. Many architectures have instructions that cannot be described by this (or where it does not make sense to describe the functionality, such as instructions for AES encryption – the optimizers cannot take advantage of this description anyway). These cases are handled by describing the instructions using an unspec or unspec_volatile expression which the compiler treats as a black box – the only knowledge the compiler has is what is described by the predicates and register constraints.

One example of how this is used the AArch64 set_fpsr insn that writes to the floating-point status register
(define_insn "set_fpsr"
  [(unspec_volatile [(match_operand:SI 0 "register_operand" "r")] UNSPECV_SET_FPSR)]
  ""
  "msr\\tfpsr, %0")
This describes a volatile instruction (i.e. an instruction with side effects) that takes a register operand. The last operand to the unspec and unspec_volatile expressions is an integer that identifies the instruction (the backend may have several different unspec instructions, and each gets a different number) – these are by convention defined as enumerations called unspec and unspecv
(define_c_enum "unspecv" [
    UNSPECV_EH_RETURN           ; Represent EH_RETURN
    UNSPECV_GET_FPCR            ; Represent fetch of FPCR content.
    UNSPECV_SET_FPCR            ; Represent assign of FPCR content.
    UNSPECV_GET_FPSR            ; Represent fetch of FPSR content.
    UNSPECV_SET_FPSR            ; Represent assign of FPSR content.
    UNSPECV_BLOCKAGE            ; Represent a blockage
    UNSPECV_PROBE_STACK_RANGE   ; Represent stack range probing.
  ])

Attributes

It is possible to add extra information to an insn (such as the length or scheduling constraints) that the back end may take advantage of when generating the code. This is done by defining attributes
(define_attr name list-of-values default)
where
  • name is a string containing the name of the attribute.
  • list-of-values is either a string that specifies a comma-separated list of values that can be assigned to the attribute, or an empty string to specify that the attribute takes numeric values.
  • default is an expression that gives the value of this attribute for insns whose definition does not include an explicit value for the attribute.
For example,
(define_attr "length" "" (const_int 2))
defines a numeric attribute “length” having the default value 2. The back end may define attributes with any name, but a few names have a specific usage in GCC. For example, the length attribute contains the instruction’s length measuerd in bytes, and is used when calculating branch distance for architectures where different instruction are used for “short” and “long” branches.

Attribute values are assigned to insns by attaching a set_attr to the define_insn as in
(define_insn "*call"
  [(call (mem:QI (match_operand:SI 0 "nonmemory_operand" "i,r"))
         (match_operand 1 "" ""))]
  ""
  "@
   jsra\\t%0
   jsr\\t%0"
  [(set_attr "length" "6,2")])
This gives the length attribute the value 6 if the first alternative was matched, and 2 if the second alternative was matched.

The attributes can be accessed from C++ code by calling the auto-generated function get_attr_name, as in
int len = get_attr_length (insn);
The return type of get_attr_name for attributes defined with a list-of-values is an enum of the possible values.

Further reading

All functionality is described in “GNU Compiler Collection Internals”:

Sunday, October 29, 2017

Excessive GCC memory usage for large std::bitset arrays

The C++ slack channel had a discussion last week about the code
#include <array>
#include <bitset>
#include <cstddef>

constexpr std::size_t N = 100000;
std::array<std::bitset<N>, N> elems;

int main() {}
that makes GCC consume about 9 gigabytes of memory in the parsing phase of the compilation. This does not happen when using C-style arrays, so changing the definition of elems to
std::bitset<N> elems[N];
makes the code compile without needing an excessive amount of memory. So why does GCC consume all this memory while parsing, and only when using std::array?

The reason has to do with deficiencies in GCC’s implementation of constexpr. To see what is happening, we start by expanding the include files and removing everything not needed for the program:
namespace std
{
  typedef long unsigned int size_t;
}

namespace std __attribute__ ((__visibility__ ("default")))
{
  template<typename _Tp, std::size_t _Nm>
    struct __array_traits
    {
      typedef _Tp _Type[_Nm];
    };

  template<typename _Tp, std::size_t _Nm>
    struct array
    {
      typedef std::__array_traits<_Tp, _Nm> _AT_Type;
      typename _AT_Type::_Type _M_elems;
    };
}

namespace std __attribute__ ((__visibility__ ("default")))
{
  template<size_t _Nw>
    struct _Base_bitset
    {
      typedef unsigned long _WordT;

      _WordT _M_w[_Nw];

      constexpr _Base_bitset() noexcept
      : _M_w() { }
    };

  template<size_t _Nb>
    class bitset
    : private _Base_bitset<((_Nb) / (8 * 8) + ((_Nb) % (8 * 8) == 0 ? 0 : 1))>
    {
    };
}

constexpr std::size_t N = 100000;
std::array<std::bitset<N>, N> elems;

int main() {}
_Base_bitset has a constexpr constructor, and this makes GCC end up building the whole elems array in memory at compile time. The array is large (1,250,400,000 bytes) and GCC need to use even more memory as it represents the array by building AST nodes for the elements.

The constexpr keyword does not mean that the compiler must evaluate at compile time – it only means that it can be evaluated at compile time if the result is used where only constant expressions are allowed. I had assumed that compile-time evaluation is not needed in this case, but GCC seems to always evaluate constexpr at compile time when instantiating templates. Anyway, GCC could use a more efficient representation that does not need to keep the whole array expanded in memory...

The C-style array is not expanded in memory at compile time as it is not defined as a template. The bitset class is still expanded, but it is small, and the compiler only wastes 12,504 bytes of memory by expanding it.

Saturday, September 16, 2017

Useful GCC warning options not enabled by -Wall -Wextra

Note: This blog post was written for GCC 7. Later versions of the compiler may have added some of these warnings to -Wall or -Wextra.

GCC can warn about questionable constructs in the source code, but most such warnings are not enabled by default – developers need to use the options -Wall and -Wextra to get all generally useful warnings. There are many additional warning options that are not enabled by -Wall -Wextra as they may produce too many false positive warnings or be targeted to a specific obscure use case, but I think a few of them (listed below) may be useful for general use.

-Wduplicated-cond

Warn about duplicated condition in if-else-if chains, such as
int foo(int a)
{
  int b;
  if (a == 0)
    b = 42;
  else if (a == 0)
    b = 43;
  return b;
}
Note: -Wduplicated-cond was added in GCC 6.


-Wduplicated-branches

Warn when an if-else has identical branches, such as
int foo(int a)
{
  int b;
  if (a == 0)
    b = 42;
  else
    b = 42;
  return b;
}
It also warns for conditional operators having identical second and third expressions
int foo(int a)
{
  int b;
  b = (a == 0) ? 42 : 42;
  return b;
}
Note: -Wduplicated-branches was added in GCC 7.


-Wlogical-op

Warn about use of logical operations where a bitwise operation probably was intended, such as
int foo(int a)
{
  a = a || 0xf0;
  return a;
}
It also warns when the operands of logical operations are the same
int foo(int a)
{
  if (a < 0 && a < 0)
    return 0;
  return 1;
}
Note: -Wlogical-op was added in GCC 4.3.


-Wrestrict

Warn when the compiler detects that an argument passed to a restrict or __restrict qualified parameter alias with another parameter.
void bar(char * __restrict, char * __restrict);

void foo(char *p)
{
  bar(p, p);
}
Note: -Wrestrict was added in GCC 7.


-Wnull-dereference

Warn when the compiler detects paths that dereferences a null pointer.
void foo(int *p, int a)
{
  int *q = 0;
  if (0 <= a && a < 10)
    q = p + a;
  *q = 1;  // q may be NULL
}
Note: -Wnull-dereference was added in GCC 6.


-Wold-style-cast

Warn if a C-style cast to a non-void type is used within a C++ program.
int *foo(void *p)
{
  return (int *)p;
}
Note: -Wold-style-cast was added before GCC 3.
Note: -Wold-style-cast is only available for C++.


-Wuseless-cast

Warn when an expression is cast to its own type within a C++ program.
int *foo(int *p)
{
  return static_cast<int *>(p);
}
Note: -Wuseless-cast was added in GCC 4.8.
Note: -Wuseless-cast is only available for C++.


-Wjump-misses-init

Warn if a goto statement or a switch statement jumps forward across the initialization of a variable, or jumps backward to a label after the variable has been initialized.
int foo(int a)
{
  int b;
  switch (a)
  {
  case 0:
    b = 0;
    int c = 42;
    break;
  default:
    b = c;  // c not initialized here
  }
  return b;
}
Note: -Wjump-misses-init was added in GCC 4.5.
Note: -Wjump-misses-init is only available for C – jumping over variable initialization is an error in C++.


-Wdouble-promotion

Warn when a value of type float is implicitly promoted to double.

Floating point constants have the type double, which makes it easy to accidentally compute in a higher precision than intended. For example,
float area(float radius)
{
  return 3.14159 * radius * radius;
}
does all the computation in double precision instead of float. There is normally no difference in performance between float and double for scalar x86 code (although there may be a big difference for small, embedded, CPUs), but double may be much slower after vectorization as only half the number of elements fit in the vectors compared to float values.

Note: -Wdouble-promotion was added in GCC 4.6.


-Wshadow

Warn when a local variable or type declaration shadows another variable, parameter, type, or class member.
int result;

int foo(int *p, int len)
{
  int result = 0;  // Shadows the global variable
  for (int i = 0; i < len; i++)
    result += p[i];
  return result;
}
Note: -Wshadow was added before GCC 3.


-Wformat=2

The -Wformat option warns when calls to printf, scanf, and similar functions have an incorrect format string or when the arguments do not have the correct type for the format string. The option is enabled by -Wall, but it can be made more aggressive by adding -Wformat=2 which adds security-related warnings. For example, it warns for
#include <stdio.h>

void foo(char *p)
{
  printf(p);
}
that may be a security hole if the format string came from untrusted input and contains ‘%n’.

Note: -Wformat=2 was added in GCC 3.0.

Tuesday, August 29, 2017

GCC target description macros and functions

This is part four of a series “Writing a GCC back end”.

The functionality that cannot be handled by the machine description in machine.md is implemented using target macros and functions in machine.h and machine.c. Starting from an existing back end that is similar to you target will give you a reasonable implementation for most of these, but you will need to update the implementation related to the register file and addressing modes to correctly describe your architecture. This blog post describes the relevant macros and functions, with examples from the Moxie back end.

Inspecting the RTL

Some of the macros and functions are given RTL expressions that they need to inspect in order to do what is expected. RTL expressions are represented by a type rtx that is accessed using macros. Assume we have a variable
rtx x;
containing the expression
(plus:SI (reg:SI 100) (const_int 42))
We can now access the different parts of it as
  • GET_CODE(x) returns the operation PLUS
  • GET_MODE(x) returns the operation’s machine mode SImode
  • XEXP(x,0) returns the first operand – an rtx corresponding to (reg:SI 100)
  • XEXP(x,1) returns the second operand – an rtx corresponding to (const_int 42)
XEXP() returns an rtx which is not right for accessing the value of a leaf expression (such as reg) – those need to be accessed using a type-specific macro. For example, the integer value from (const_int 42) is accessed using INTVAL(), and the register number from (reg:SI 100) is accessed using REGNO().

For an example of how this is used in the back end, suppose our machine have load and store instructions that accept an address that is a constant, a register, or a register plus a constant in the range [-32768, 32767], and we need a function that given an address expression tells if it is a valid address expression for the target. We could write that function as
bool
valid_address_p (rtx x)
{
  if (GET_CODE (x) == SYMBOL_REF
      || GET_CODE (x) == LABEL_REF
      || GET_CODE (x) == CONST)
    return true;
  if (REG_P (x))
    return true;
  if (GET_CODE(x) == PLUS
      && REG_P (XEXP (x, 0))
      && CONST_INT_P (XEXP (x, 1))
      && IN_RANGE (INTVAL (XEXP (x, 1)), -32768, 32767))
    return true;
  return false;
}

Registers

The register names are specified as
#deefine REGISTER_NAMES             \
  {                                 \
    "$fp", "$sp", "$r0", "$r1",     \
    "$r2", "$r3", "$r4", "$r5",     \
    "$r6", "$r7", "$r8", "$r9",     \
    "$r10", "$r11", "$r12", "$r13", \
    "?fp", "?ap", "$pc", "?cc"      \
  }
The names are used for generating the assembly files, and for the GCC extension letting programmers place variables in specified registers
register int *foo asm ("$r12");
The Moxie architecture does only have 18 registers, but it adds two fake registers ?fp and ?ap for the frame pointer and argument pointer (used to access the function’s argument lists). This is a common strategy in GCC back ends to simplify code generation and elimination of unneeded frame pointer and argument pointers, and there there is a mechanism (ELIMINABLE_REGS) that rewrites these fake registers to real registers (such as the stack pointer).

The GCC RTL contains virtual registers (which are called pseudo registers in GCC) before the real registers (called hard registers) are allocated. The registers are represented as an integer, where 0 is the first hard register, 1 is the second hard register, etc., and the pseudo registers follows after the last hard register. The back end specifies the number of hard registers by specifying the first pseudo register
#define FIRST_PSEUDO_REGISTER 20

Some of the registers, such as the program counter $pc, cannot be used by the register allocator, so we need to specify which registers the register allocator must avoid
#define FIXED_REGISTERS             \
  {                                 \
    1, 1, 0, 0,                     \
    0, 0, 0, 0,                     \
    0, 0, 0, 0,                     \
    0, 0, 0, 1,                     \
    1, 1, 1, 1                      \
  }
where 1 means that it cannot be used by the register allocator. Similarly, the register allocator needs to know which registers may be changed by calling a function
#define CALL_USED_REGISTERS         \
  {                                 \
    1, 1, 1, 1,                     \
    1, 1, 1, 1,                     \
    0, 0, 0, 0,                     \
    0, 0, 1, 1,                     \
    1, 1, 1, 1                      \
  }
where 1 means that the register is clobbered by function calls.

It is possible to specify the order in which the register allocator allocates the registers. The Moxie back end does not do this, but it could have done it with something like the code below that would use register 2 ($r0) for the first allocated register, register 3 ($r1) for the second allocated register, etc.
#define REG_ALLOC_ORDER                     \
  {                                         \
    /* Call-clobbered registers */          \
    2, 3, 4, 5, 6, 7, 14                    \
    /* Call-saved registers */              \
    8, 9, 10, 11, 12, 13,                   \
    /* Registers not for general use.  */   \
    0, 1, 15, 16, 17, 18, 19                \
  }
For an example of where this can be used, consider an architecture with 16 registers and “push multiple” and “pop multiple” instructions that push/pop the last \(n\) registers. The instructions are designed to store call-saved registers when calling a function, and it makes sense to allocate the call-saved registers in reverse order so the push/pop instructions save as few registers as possible
#define REG_ALLOC_ORDER                         \
  {                                             \
    /* Call-clobbered registers */              \
    0, 1, 2, 3,                                 \
    /* Call-saved registers */                  \
    15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4    \
  }

There are usually restrictions on how registers can be used (e.g. the Moxie $pc register cannot be used in arithmetic instructions), and these restrictions are described by register classes. Each register class defines a set of registers that can be used in the same way (for example, that can be used in arithmetic instructions). There are three standard register classes that must be defined
  • ALL_REGS – containing all of the registers.
  • NO_REGS – containing no registers.
  • GENERAL_REGS – used for the ‘r’ and ‘g’ constraints.
and the back end can add its own as needed. The register classes are implemented as
enum reg_class
{
  NO_REGS,
  GENERAL_REGS,
  SPECIAL_REGS,
  CC_REGS,
  ALL_REGS,
  LIM_REG_CLASSES
};

#define N_REG_CLASSES LIM_REG_CLASSES

#define REG_CLASS_NAMES             \
  {                                 \
    "NO_REGS",                      \
    "GENERAL_REGS",                 \
    "SPECIAL_REGS",                 \
    "CC_REGS",                      \
    "ALL_REGS"                      \
  }

#define REG_CLASS_CONTENTS                           \
  {                                                  \
    { 0x00000000 }, /* Empty */                      \
    { 0x0003FFFF }, /* $fp, $sp, $r0 to $r13, ?fp */ \
    { 0x00040000 }, /* $pc */                        \
    { 0x00080000 }, /* ?cc */                        \
    { 0x000FFFFF }  /* All registers */              \
  }
REG_CLASS_CONTENTS consists of a bit mask for each register class, telling with registers are in the set. Architectures having more than 32 registers use several values for each register class, such as
    { 0xFFFFFFFF, 0x000FFFFF }  /* All registers */  \

The back end also need to implement the REGNO_REG_CLASS macro that returns the register class for a register. There are in general several register classes containing the registers, and the macro should return a minimal class (i.e. one for which no smaller class contains the register)
#define REGNO_REG_CLASS(REGNO) moxie_regno_to_class[ (REGNO) ]

const enum reg_class riscv_regno_to_class[FIRST_PSEUDO_REGISTER] = {
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  GENERAL_REGS, GENERAL_REGS, SPECIAL_REGS, CC_REGS
};

The back end may also need to set a few target macros detailing how values fit in registers, such as
/* A C expression that is nonzero if it is permissible to store a
   value of mode MODE in hard register number REGNO (or in several
   registers starting with that one).  */ 
#define HARD_REGNO_MODE_OK(REGNO,MODE) 1
See section 17.7.2 in “GNU Compiler Collection Internals” for a list of those macros.

Addressing modes

Addressing modes varies a bit between architectures – most can do “base + index” addressing, but there are often restrictions on the format of the index etc. The general functionality is described by five macros:
/* Specify the machine mode that pointers have.
   After generation of rtl, the compiler makes no further distinction
   between pointers and any other objects of this machine mode.  */
#define Pmode SImode

/* Maximum number of registers that can appear in a valid memory
   address.  */
#define MAX_REGS_PER_ADDRESS 1

/* A macro whose definition is the name of the class to which a
   valid base register must belong.  A base register is one used in
   an address which is the register value plus a displacement.  */
#define BASE_REG_CLASS GENERAL_REGS

/* A macro whose definition is the name of the class to which a
   valid index register must belong.  An index register is one used
   in an address where its value is either multiplied by a scale
   factor or added to another register (as well as added to a
   displacement).  */
#define INDEX_REG_CLASS NO_REGS

/* A C expression which is nonzero if register number NUM is suitable
   for use as a base register in operand addresses.  */
#ifdef REG_OK_STRICT
#define REGNO_OK_FOR_BASE_P(NUM)   \
  (HARD_REGNO_OK_FOR_BASE_P(NUM)    \
   || HARD_REGNO_OK_FOR_BASE_P(reg_renumber[(NUM)]))
#else
#define REGNO_OK_FOR_BASE_P(NUM)   \
  ((NUM) >= FIRST_PSEUDO_REGISTER || HARD_REGNO_OK_FOR_BASE_P(NUM))
#endif

/* A C expression which is nonzero if register number NUM is suitable
   for use as an index register in operand addresses.  */
#define REGNO_OK_FOR_INDEX_P(NUM) 0
where
#define HARD_REGNO_OK_FOR_BASE_P(NUM) \
  ((unsigned) (NUM) < FIRST_PSEUDO_REGISTER \
   && (REGNO_REG_CLASS(NUM) == GENERAL_REGS \
       || (NUM) == HARD_FRAME_POINTER_REGNUM))
is a helper macro the Moxie back end has introduced.

The macros REGNO_OK_FOR_BASE_P and REGNO_OK_FOR_INDEX_P have two versions – the normal version that accepts all pseudo registers as well as the valid hard registers, and one “strict” version that only accepts the valid hard registers. The strict version is used from within the register allocator, so it needs to check pseudo registers against the reg_number array to accept cases where the pseudo register is in the process of being allocated to a hard register.

The back end also needs to implement the TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P hook that is used by the compiler to check that the address expression is valid (this is needed as the hardware may have more detailed constraints that cannot be expressed by the macros above). This is done similarly to the valid_address_p example in “inspecting the IR” above, but it needs to handle the “strict” case too. The Moxie back end implements this as
static bool
moxie_reg_ok_for_base_p (const_rtx reg, bool strict_p)
{
  int regno = REGNO (reg);

  if (strict_p)
    return HARD_REGNO_OK_FOR_BASE_P (regno)
    || HARD_REGNO_OK_FOR_BASE_P (reg_renumber[regno]);
  else    
    return !HARD_REGISTER_NUM_P (regno)
    || HARD_REGNO_OK_FOR_BASE_P (regno);
}

static bool
moxie_legitimate_address_p (machine_mode mode ATTRIBUTE_UNUSED,
       rtx x, bool strict_p,
       addr_space_t as)
{
  gcc_assert (ADDR_SPACE_GENERIC_P (as));

  if (GET_CODE(x) == PLUS
      && REG_P (XEXP (x, 0))
      && moxie_reg_ok_for_base_p (XEXP (x, 0), strict_p)
      && CONST_INT_P (XEXP (x, 1))
      && IN_RANGE (INTVAL (XEXP (x, 1)), -32768, 32767))
    return true;
  if (REG_P (x) && moxie_reg_ok_for_base_p (x, strict_p))
    return true;
  if (GET_CODE (x) == SYMBOL_REF
      || GET_CODE (x) == LABEL_REF
      || GET_CODE (x) == CONST)
    return true;
  return false;
}

#undef  TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P
#define TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P moxie_legitimate_address_p

Finally, TARGET_PRINT_OPERAND_ADDRESS must be implemented to output the correct syntax for the address expressions in the assembly file generated by the compiler:
static void
moxie_print_operand_address (FILE *file, machine_mode, rtx x)
{
  switch (GET_CODE (x))
    {
    case REG:
      fprintf (file, "(%s)", reg_names[REGNO (x)]);
      break;
      
    case PLUS:
      switch (GET_CODE (XEXP (x, 1)))
 {
 case CONST_INT:
   fprintf (file, "%ld(%s)", 
     INTVAL(XEXP (x, 1)), reg_names[REGNO (XEXP (x, 0))]);
   break;
 case SYMBOL_REF:
   output_addr_const (file, XEXP (x, 1));
   fprintf (file, "(%s)", reg_names[REGNO (XEXP (x, 0))]);
   break;
 case CONST:
   {
     rtx plus = XEXP (XEXP (x, 1), 0);
     if (GET_CODE (XEXP (plus, 0)) == SYMBOL_REF 
  && CONST_INT_P (XEXP (plus, 1)))
       {
  output_addr_const(file, XEXP (plus, 0));
  fprintf (file,"+%ld(%s)", INTVAL (XEXP (plus, 1)),
    reg_names[REGNO (XEXP (x, 0))]);
       }
     else
       abort();
   }
   break;
 default:
   abort();
 }
      break;

    default:
      output_addr_const (file, x);
      break;
    }
}

#undef  TARGET_PRINT_OPERAND_ADDRESS
#define TARGET_PRINT_OPERAND_ADDRESS moxie_print_operand_address

Further reading

All functionality is described in “GNU Compiler Collection Internals”:
  • Chapter 13 describes the RTL representation, including macros and functions for accessing it.
  • Chapter 17 describes all target description macros and functions.