Skip to content

Commit

Permalink
ENH: Raise C++ standard to C++17
Browse files Browse the repository at this point in the history
  • Loading branch information
seiko2plus committed Apr 4, 2023
1 parent 08e7784 commit d183edf
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 78 deletions.
2 changes: 1 addition & 1 deletion meson.build
Expand Up @@ -11,7 +11,7 @@ project(
'buildtype=debugoptimized',
'b_ndebug=if-release',
'c_std=c99',
'cpp_std=c++14',
'cpp_std=c++17',
'blas=openblas',
'lapack=openblas',
'pkgconfig.relocatable=true',
Expand Down
42 changes: 1 addition & 41 deletions numpy/core/setup.py
Expand Up @@ -405,7 +405,6 @@ def configuration(parent_package='',top_path=None):
exec_mod_from_location)
from numpy.distutils.system_info import (get_info, blas_opt_info,
lapack_opt_info)
from numpy.distutils.ccompiler_opt import NPY_CXX_FLAGS
from numpy.version import release as is_released

config = Configuration('core', parent_package, top_path)
Expand Down Expand Up @@ -658,44 +657,6 @@ def get_mathlib_info(*args):
# but we cannot use add_installed_pkg_config here either, so we only
# update the substitution dictionary during npymath build
config_cmd = config.get_config_cmd()
# Check that the toolchain works, to fail early if it doesn't
# (avoid late errors with MATHLIB which are confusing if the
# compiler does not work).
for lang, test_code, note in (
('c', 'int main(void) { return 0;}', ''),
('c++', (
'int main(void)'
'{ auto x = 0.0; return static_cast<int>(x); }'
), (
'note: A compiler with support for C++11 language '
'features is required.'
)
),
):
is_cpp = lang == 'c++'
if is_cpp:
# this a workaround to get rid of invalid c++ flags
# without doing big changes to config.
# c tested first, compiler should be here
bk_c = config_cmd.compiler
config_cmd.compiler = bk_c.cxx_compiler()

# Check that Linux compiler actually support the default flags
if hasattr(config_cmd.compiler, 'compiler'):
config_cmd.compiler.compiler.extend(NPY_CXX_FLAGS)
config_cmd.compiler.compiler_so.extend(NPY_CXX_FLAGS)

st = config_cmd.try_link(test_code, lang=lang)
if not st:
# rerun the failing command in verbose mode
config_cmd.compiler.verbose = True
config_cmd.try_link(test_code, lang=lang)
raise RuntimeError(
f"Broken toolchain: cannot link a simple {lang.upper()} "
f"program. {note}"
)
if is_cpp:
config_cmd.compiler = bk_c
mlibs = check_mathlib(config_cmd)

posix_mlib = ' '.join(['-l%s' % l for l in mlibs])
Expand Down Expand Up @@ -1067,8 +1028,7 @@ def generate_umath_doc_header(ext, build_dir):
common_deps,
libraries=['npymath'],
extra_objects=svml_objs,
extra_info=extra_info,
extra_cxx_compile_args=NPY_CXX_FLAGS)
extra_info=extra_info)

#######################################################################
# umath_tests module #
Expand Down
4 changes: 3 additions & 1 deletion numpy/core/src/npymath/npy_math_private.h
Expand Up @@ -21,6 +21,7 @@
#include <Python.h>
#ifdef __cplusplus
#include <cmath>
#include <complex>
using std::isgreater;
using std::isless;
#else
Expand Down Expand Up @@ -494,8 +495,9 @@ do { \
* Microsoft C defines _MSC_VER
* Intel compiler does not use MSVC complex types, but defines _MSC_VER by
* default.
* since c++17 msvc is no longer support them.
*/
#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
#if !defined(__cplusplus) && defined(_MSC_VER) && !defined(__INTEL_COMPILER)
typedef union {
npy_cdouble npy_z;
_Dcomplex c99_z;
Expand Down
11 changes: 1 addition & 10 deletions numpy/distutils/ccompiler_opt.py
Expand Up @@ -16,15 +16,6 @@
import subprocess
import textwrap

# These flags are used to compile any C++ source within Numpy.
# They are chosen to have very few runtime dependencies.
NPY_CXX_FLAGS = [
'-std=c++11', # Minimal standard version
'-D__STDC_VERSION__=0', # for compatibility with C headers
'-fno-exceptions', # no exception support
'-fno-rtti'] # no runtime type information


class _Config:
"""An abstract class holds all configurable attributes of `CCompilerOpt`,
these class attributes can be used to change the default behavior
Expand Down Expand Up @@ -1000,7 +991,7 @@ def __init__(self):
)
detect_args = (
("cc_has_debug", ".*(O0|Od|ggdb|coverage|debug:full).*", ""),
("cc_has_native",
("cc_has_native",
".*(-march=native|-xHost|/QxHost|-mcpu=a64fx).*", ""),
# in case if the class run with -DNPY_DISABLE_OPTIMIZATION
("cc_noopt", ".*DISABLE_OPT.*", ""),
Expand Down
1 change: 1 addition & 0 deletions numpy/distutils/command/build_clib.py
Expand Up @@ -307,6 +307,7 @@ def build_a_library(self, build_info, lib_name, libraries):
# problem, msvc uses its own convention :(
c_sources += cxx_sources
cxx_sources = []
extra_cflags += extra_cxxflags

# filtering C dispatch-table sources when optimization is not disabled,
# otherwise treated as normal sources.
Expand Down
1 change: 1 addition & 0 deletions numpy/distutils/command/build_ext.py
Expand Up @@ -407,6 +407,7 @@ def build_extension(self, ext):
if cxx_sources:
# Needed to compile kiva.agg._agg extension.
extra_args.append('/Zm1000')
extra_cflags += extra_cxxflags
# this hack works around the msvc compiler attributes
# problem, msvc uses its own convention :(
c_sources += cxx_sources
Expand Down
2 changes: 0 additions & 2 deletions numpy/linalg/setup.py
Expand Up @@ -4,7 +4,6 @@

def configuration(parent_package='', top_path=None):
from numpy.distutils.misc_util import Configuration
from numpy.distutils.ccompiler_opt import NPY_CXX_FLAGS
from numpy.distutils.system_info import get_info, system_info
config = Configuration('linalg', parent_package, top_path)

Expand Down Expand Up @@ -81,7 +80,6 @@ def get_lapack_lite_sources(ext, build_dir):
sources=['umath_linalg.cpp', get_lapack_lite_sources],
depends=['lapack_lite/f2c.h'],
extra_info=lapack_info,
extra_cxx_compile_args=NPY_CXX_FLAGS,
libraries=['npymath'],
)
config.add_data_files('*.pyi')
Expand Down
138 changes: 115 additions & 23 deletions setup.py
Expand Up @@ -12,7 +12,9 @@
import warnings
import builtins
import re
import tempfile

from distutils.errors import CompileError

# Python supported version checks. Keep right after stdlib imports to ensure we
# get a sensible error for older Python versions
Expand Down Expand Up @@ -184,45 +186,135 @@ def run(self):

def get_build_overrides():
"""
Custom build commands to add `-std=c99` to compilation
Custom build commands to add std flags if required to compilation
"""
from numpy.distutils.command.build_clib import build_clib
from numpy.distutils.command.build_ext import build_ext
from numpy._utils import _pep440

def _needs_gcc_c99_flag(obj):
if obj.compiler.compiler_type != 'unix':
return False
def try_compile(compiler, file, flags = [], verbose=False):
# To bypass trapping warnings by Travis CI
if getattr(compiler, 'compiler_type', '') == 'unix':
flags = ['-Werror'] + flags
bk_ver = getattr(compiler, 'verbose', False)
compiler.verbose = verbose
try:
compiler.compile([file], extra_postargs=flags)
return True, ''
except CompileError as e:
return False, str(e)
finally:
compiler.verbose = bk_ver

def flags_is_required(compiler, is_cpp, flags, code):
if is_cpp:
compiler = compiler.cxx_compiler()
suf = '.cpp'
else:
suf = '.c'
with tempfile.TemporaryDirectory() as temp_dir:
tmp_file = os.path.join(temp_dir, "test" + suf)
with open(tmp_file, "w+") as f:
f.write(code)
# without specify any flags in case of the required
# standard already supported by default, then there's
# no need for passing the flags
comp = try_compile(compiler, tmp_file)
if not comp[0]:
comp = try_compile(compiler, tmp_file, flags)
if not comp[0]:
# rerun to verbose the error
try_compile(compiler, tmp_file, flags, True)
if is_cpp:
raise RuntimeError(
"Broken toolchain during testing C++ compiler. \n"
"A compiler with support for C++17 language "
"features is required.\n"
f"Triggered the following error: {comp[1]}."
)
else:
raise RuntimeError(
"Broken toolchain during testing C compiler. \n"
"A compiler with support for C99 language "
"features is required.\n"
f"Triggered the following error: {comp[1]}."
)
return True
return False

cc = obj.compiler.compiler[0]
if "gcc" not in cc:
return False

# will print something like '4.2.1\n'
out = subprocess.run([cc, '-dumpversion'],
capture_output=True, text=True)
# -std=c99 is default from this version on
if _pep440.parse(out.stdout) >= _pep440.Version('5.0'):
return False
return True
def std_cxx_flags(cmd):
compiler = cmd.compiler
flags = getattr(compiler, '__np_cache_cpp_flags', None)
if flags is not None:
return flags
flags = dict(
msvc = ['/std:c++17']
).get(compiler.compiler_type, ['-std=c++17'])
# These flags are used to compile any C++ source within Numpy.
# They are chosen to have very few runtime dependencies.
extra_flags = dict(
# to update #def __cplusplus with enabled C++ version
msvc = ['/Zc:__cplusplus']
).get(compiler.compiler_type, [
# The following flag is used to avoid emit any extra code
# from STL since extensions are build by C linker and
# without C++ runtime dependencies.
'-fno-threadsafe-statics',
'-D__STDC_VERSION__=0', # for compatibility with C headers
'-fno-exceptions', # no exception support
'-fno-rtti' # no runtime type information
])
if not flags_is_required(compiler, True, flags, textwrap.dedent('''
#include <type_traits>
template<typename ...T>
constexpr bool test_fold = (... && std::is_const_v<T>);
int main()
{
if constexpr (test_fold<int, const int>) {
return 0;
}
else {
return -1;
}
}
''')):
flags.clear()
flags += extra_flags
setattr(compiler, '__np_cache_cpp_flags', flags)
return flags

def std_c_flags(cmd):
compiler = cmd.compiler
flags = getattr(compiler, '__np_cache_c_flags', None)
if flags is not None:
return flags
flags = dict(
msvc = []
).get(compiler.compiler_type, ['-std=c99'])

if not flags_is_required(compiler, False, flags, textwrap.dedent('''
inline int test_inline() { return 0; }
int main(void)
{ return test_inline(); }
''')):
flags.clear()

setattr(compiler, '__np_cache_c_flags', flags)
return flags

class new_build_clib(build_clib):
def build_a_library(self, build_info, lib_name, libraries):
from numpy.distutils.ccompiler_opt import NPY_CXX_FLAGS
if _needs_gcc_c99_flag(self):
build_info['extra_cflags'] = ['-std=c99']
build_info['extra_cxxflags'] = NPY_CXX_FLAGS
build_info['extra_cflags'] = std_c_flags(self)
build_info['extra_cxxflags'] = std_cxx_flags(self)
build_clib.build_a_library(self, build_info, lib_name, libraries)

class new_build_ext(build_ext):
def build_extension(self, ext):
if _needs_gcc_c99_flag(self):
if '-std=c99' not in ext.extra_compile_args:
ext.extra_compile_args.append('-std=c99')
ext.extra_c_compile_args += std_c_flags(self)
ext.extra_cxx_compile_args += std_cxx_flags(self)
build_ext.build_extension(self, ext)
return new_build_clib, new_build_ext


def generate_cython():
# Check Cython version
from numpy._utils import _pep440
Expand Down

0 comments on commit d183edf

Please sign in to comment.