Search code examples
cx86autotoolsautoconf

How to use an AC_CHECK_HEADER header in an AC_COMPILE_IFELSE program


I'm performing a header check for a non-standard <x86intrin.h> header provided by Clang and GCC. Other compilers follow Intel and use <immintrin.h>:

AC_CHECK_HEADER([x86intrin.h])

Autotools finds it on the Linux test systems:

checking x86intrin.h usability... yes
checking x86intrin.h presence... yes
checking for x86intrin.h... yes

I then use it in a test:

CXXFLAGS="-mrdseed"
XXX_PROGRAM="#include <immintrin.h>
   #ifdef HAVE_X86INTRIN_H
   # include <x86intrin.h>
   #endif
   int main(int argc, char** argv) {
      unsigned int x;
      return _rdseed32_step(&x);
   }"

AC_MSG_CHECKING([if $CXXNAME supports $CXXFLAGS])
AC_COMPILE_IFELSE(
   [AC_LANG_SOURCE([$XXX_PROGRAM])],
   [AC_MSG_RESULT([yes])],
   [AC_MSG_RESULT([no])]
)

The test fails:

checking if g++ supports -mrdseed... no

The machine has the feature and the compiler supports it. Looking at condefs.h from config.log shows HAVE_X86INTRIN_H is not being set as expected.

The manual shows how to use a header resulting from a check at 5.6.3 Generic Header Checks. I think I am doing roughly the same thing:

AC_CHECK_HEADERS([foo.h])
AC_CHECK_HEADERS([bar.h], [], [],
[#ifdef HAVE_FOO_H
 # include <foo.h>
 # endif
])

How do I use an AC_CHECK_HEADER header in an AC_COMPILE_IFELSE program?


Here is my test driver:

$ cat test.cxx
#include <immintrin.h>
#ifdef HAVE_X86INTRIN_H
# include <x86intrin.h>
#endif
int main(int argc, char** argv) {
    unsigned int x;
    return _rdseed32_step(&x);
}

$ g++ -mrdseed test.cxx -o test.exe
test.cxx: In function ‘int main(int, char**)’:
test.cxx:7:12: error: ‘_rdseed32_step’ was not declared in this scope
     return _rdseed32_step(&x);
            ^~~~~~~~~~~~~~
test.cxx:7:12: note: suggested alternative: ‘_rdrand32_step’
     return _rdseed32_step(&x);
            ^~~~~~~~~~~~~~
            _rdrand32_step

$ g++ -DHAVE_X86INTRIN_H -mrdseed test.cxx -o test.exe
$ ./test.exe
$

Here is the distro and compiler:

$ lsb_release -a
LSB Version:    :core-4.1-amd64:core-4.1-noarch
Distributor ID: Fedora
Description:    Fedora release 28 (Twenty Eight)
Release:        28

$ g++ --version
g++ (GCC) 8.1.1 20180712 (Red Hat 8.1.1-5)

Here are the relevant parts from config.log:

configure:17624: checking if g++ supports -mrdseed
configure:17631: g++ -o conftest -mrdseed   conftest.cpp  >&5
conftest.cpp: In function 'int main(int, char**)':
conftest.cpp:38:17: error: '_rdseed32_step' was not declared in this scope
          return _rdseed32_step(&x);
                 ^~~~~~~~~~~~~~
conftest.cpp:38:17: note: suggested alternative: '_rdrand32_step'
          return _rdseed32_step(&x);
                 ^~~~~~~~~~~~~~
                 _rdrand32_step
configure:17631: $? = 1
configure: failed program was:
| /* confdefs.h */
| #define PACKAGE_NAME "Crypto++"
| ...
| #define STDC_HEADERS 1
| #define HAVE_SYS_TYPES_H 1
| #define HAVE_SYS_STAT_H 1
| #define HAVE_STDLIB_H 1
| #define HAVE_STRING_H 1
| #define HAVE_MEMORY_H 1
| #define HAVE_STRINGS_H 1
| #define HAVE_INTTYPES_H 1
| #define HAVE_STDINT_H 1
| #define HAVE_UNISTD_H 1
| #define HAVE_DLFCN_H 1
| #define LT_OBJDIR ".libs/"
| ...
| /* end confdefs.h.  */
| #include <immintrin.h>
|       #ifdef HAVE_X86INTRIN_H
|       # include <x86intrin.h>
|       #endif
|       int main(int argc, char** argv) {
|          unsigned int x;
|          return _rdseed32_step(&x);
|       }
configure:17645: result: no

Solution

  • You have tripped over an Autoconf gotcha. The docs for AC_CHECK_HEADER say:

    If the system header file header-file is compilable, execute shell commands action-if-found, otherwise execute action-if-not-found. If you just want to define a symbol if the header file is available, consider using AC_CHECK_HEADERS instead.

    (Emphasis added)

    Contrast that with the docs for AC_CHECK_HEADERS:

    For each given system header file header-file in the blank-separated argument list that exists, define HAVE_*header-file* (in all capitals). If action-if-found is given, [...]

    Note that unlike those for AC_CHECK_HEADERS, the docs for AC_CHECK_HEADER do not claim that any symbol will be defined if the header is found. In that case, the only thing done other than to report the result of the check is to run the commands in action-if-found. You can certainly put an AC_DEFINE in there to define a symbol, but you don't get that for free the way you do from AC_CHECK_HEADERS. If you want that, you can use AC_CHECK_HEADERS even for a single header.

    I'm sure original the reason for the difference revolves around the fact that AC_CHECK_HEADERS checks possibly many headers. Additionally, sometimes you don't care about defining a symbol, or perhaps you expressly want to avoid doing so; for those cases you can use AC_CHECK_HEADER.

    Here's a complete Autoconf input file that demonstrates a working variation on your check:

    AC_PREREQ([2.69])
    AC_INIT([test], [0.0.0])
    
    # Checks for programs.
    AC_PROG_CC
    AC_PROG_CXX
    
    # Ensure that tests are run with the C++ compiler
    AC_LANG([C++])
    
    # Checks for header files.
    AC_CHECK_HEADERS([x86intrin.h])
    
    # Check support for -mrdseed
    AS_IF([test "$ac_cv_header_x86intrin_h" = "yes"], [
      CXXFLAGS_save=$CXXFLAGS
      CXXFLAGS="$CXXFLAGS -mrdseed"
    
      XXX_PROGRAM="
    #include <immintrin.h>
    #ifdef HAVE_X86INTRIN_H
    #include <x86intrin.h>
    #endif
    int main(void) {
        unsigned int x;
        return _rdseed32_step(&x);
    }
    "
      AC_MSG_CHECKING([whether $CXX supports -mrdseed])
      AC_COMPILE_IFELSE(
        [AC_LANG_SOURCE([$XXX_PROGRAM])],
        [AC_MSG_RESULT([yes])],
        [AC_MSG_RESULT([no])]
      )
    
      CXXFLAGS=$CXXFLAGS_save
    ])
    
    AC_OUTPUT