I’m trying to add support for the ISPC language (and compiler) to cmake.
I based my first iteration from what I could gather from the ASM, Fortran and CUDA languages modules, but sadly it seems I am missing something.
The compiler is detected correctly, seems to work, however files are not compiled.
I do set the CMAKE_ISPC_SOURCE_FILE_EXTENSIONS variable to ispc but only my .cpp files are compiled to .obj, and the .ispc file is ignored.
This is the output from cmake configuration:
-- Building for: Visual Studio 16 2019
-- Selecting Windows SDK version 10.0.18362.0 to target Windows 10.0.18363.
-- The C compiler identification is MSVC 19.24.28314.0
-- The CXX compiler identification is MSVC 19.24.28314.0
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.24.28314/bin/Hostx64/x64/cl.exe
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.24.28314/bin/Hostx64/x64/cl.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.24.28314/bin/Hostx64/x64/cl.exe
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.24.28314/bin/Hostx64/x64/cl.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Check for working ISPC compiler: D:/tools/bin/ispc.exeD:/tools/bin/ispc.exe
-- Check for working ISPC compiler: D:/tools/bin/ispc.exeD:/tools/bin/ispc.exe -- works
-- Configuring done
-- Generating done
-- Build files have been written to: D:/cmake/ispc-language/build
And when building:
Checking Build System
Building Custom Rule D:/cmake/ispc-language/CMakeLists.txt
Building Custom Rule D:/cmake/ispc-language/CMakeLists.txt
a.cpp
b.cpp
Generating Code...
libispc.vcxproj -> D:\cmake\ispc-language\build\Debug\libispc.lib
Building Custom Rule D:/cmake/ispc-language/CMakeLists.txt
This may require additional support from the VS generator itself. See for example the code here that maps from source file language to the corresponding .vcxproj tool.
Alternatively you can try focusing on getting it to work with the NMake Makefiles or Ninja generators at a command prompt with the appropriate environment loaded.
Thanks I will take a look.
Is the VS generator only using prebuilt msbuild targets ?
I suppose I might have to create my own targets for the VS generator if I need to support it.
I’ll try with Ninja too.
CMake Error: Error required internal CMake variable not set, cmake may not be built correctly.
Missing variable is:
CMAKE_ISPC_LINK_EXECUTABLE
The thing is, ISPC relies on the C linker as it can only generate object files.
I saw that some languages define CMAKE_<LANG>_LINK_EXECUTABLE to <LANG>_NO_LINK_EXECUTABLE (such as CSharp) but I am not sure it is the right thing to do. (Mainly because then we can’t do a try_compile).
I thought it would be the same for ASM but it actually uses the same executable both for compiling and linking.
Is there a way to tell CMake that ISPC needs to use C/CXX or any other linker based on what the user asks ?
if(NOT CMAKE_C_COMPILER_LOADED OR NOT DEFINED CMAKE_C_LINK_EXECUTABLE)
message(FATAL_ERROR "C must be enabled before ISPC")
endif()
set(CMAKE_ISPC_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}")
It seems it could work to some extent, but I’m now encountering another issue I forgot about:
ispc generates headers for communication with C/C++. Maybe the other option would be to keep an invalid value for the linker and add set(CMAKE_ISPC_LINKER_PREFERENCE 0) to enforce any other linker ?
Obvisouly, it means there is a dependency between a C or C++ file using an ISPC function.
I’m not sure anymore if adding ISPC as language in CMake is a good idea (even though it is a language) or if we should go the custom target route.
I don’t think CMake supports dependencies for source files inside a target itself ? That would probably mean users need to always put their ISPC source in an individual target (might be the logical thing to do though ?).
Right now I’m bypassing the try_compile call for testing purposes (otherwise it tries to compile the .cpp file before the .ispc one, and won’t find the header).
So the questions I think need answers are:
Should ISPC be supported as a language or a module that creates custom targets ?
Can CMake understand somehow that a language can generate both object and headers ? Or maybe it doesn’t need to know ? It at least needs to know about the compilation order and the additional include folder. (We could document ISPC targets shouldn’t be mixed with other languages directly)
Should headers generated by ISPC be considered PRIVATE or PUBLIC (I think private but I’m not 100% sure)
This is touching on the general idea of domain-specific languages that produce C sources or headers. Lexer and parser generators like flex and bison are other examples. We do not currently have a first-class way to model that, so we typically go with the add_custom_command/add_custom_target approach.
I suggest using the custom command/target approach for ISPC for now. The custom commands can even be part of regular C or C++ targets because we already support generated header files in custom commands where the headers are then used to compile the C or C++ sources.
The headers generated by ISPC do not need to be explicitly considered private or public and do not need to be implicitly added to the PUBLIC_HEADER or PRIVATE_HEADER target properties at all. We don’t do that for normal C headers either. Projects can specify those explicitly or handle installation themselves via install(FILES).
So I’m making progress by using target_sources + add_custom_command but I’m hitting another issue: architecture detection.
I found a few issues related to CMAKE_<LANG>_COMPILER_ARCHITECTURE_ID such as https://gitlab.kitware.com/cmake/cmake/issues/17702 and the documentation states that it is “An internal variable subject to change.”.
I found that android and osx have their own CMAKE_ANDROID_ARCH_ABI and CMAKE_OSX_ARCHITECTURES.
Since I would like for ISPC support to be upstreamed at some point, what should I be using ?
Should I populate CMAKE_ISPC_COMPILER_ARCHITECTURE_ID based on the languages of the current project (most likely CMAKE_C_COMPILER_ARCHITECTURE_ID or CMAKE_CXX_COMPILER_ARCHITECTURE_ID ? I saw from the issue that one was not supposed to set those variables from the cache, should I provide another variable in case detection fails ?
If possible I’d like to deduce the target architecture correctly when someone uses a toolchain file, but from what I understand I might need to detect it from various variables depending on the toolchain ?
CMAKE_<LANG>_COMPILER_ARCHITECTURE_ID is meant for use by code within CMake that needs to know the architecture. It can be used for supporting custom languages externally, but with the caveat about support mentioned in https://gitlab.kitware.com/cmake/cmake/-/blob/v3.16.4/Modules/CMakeAddNewLanguage.txt. Of course if ISPC support were upstreamed that wouldn’t be a concern.
I now have a decent function that compiles things correctly but I wanted to support CMAKE_ISPC_FLAGS_ to some extent. I’d need to be able to choose the right variable at generation time.
I however didn’t find a good way to do it using genexpressions. (since variables would be expanded at configuration time).
The “trick” I thought about was to use file properties but gen expressions can only retrieve target properties. I could put the properties in the target but there might be a better way?
To be clear I’m trying to do the equivalent of ${CMAKE_ISPC_FLAGS_$<CONFIG>} in my add_custom_command COMMAND
except that the flag variables normally hold command-line strings with whitespace-separated, possibly quoted arguments. You’d have to separate those first into other variables and then reference those others in the generator expressions.
It seems to work, I ended up using logic similar to the cmake_initialize_per_config_variable (from Modules\CMakeInitializeConfigs.cmake) to concatenate genexpressions for all build types of interest. I indeed needed the COMMAND_EXPAND_LISTS so that add_custom_command would not put quotes around the flags.
So I made great progress with a FindISPC.cmake file but I still have a few issues.
One of them is that on my linux distribution (linux mint 19.3, based on ubuntu 18.04LTS), it seems that gcc defaults to using PIE.
This leads to issues because by default I chosed not to emit --pic for ISPC commands, respecting the POSITION_INDEPENDENT_CODE property of my targets.
Is there a way to check if the compiler requires --pic or not ? I know there is CheckPIESupported but it only checks if it is supported, not if it is actually enabled by default.
Should I actually use -fPIC if POSITION_INDEPENDENT_CODE is not set ? I’m a bit worried about the potential performance impact. Ideally if something could tell me if PIC/PIE is enabled by default by the linker it would be great.
There is no way to check if compiler option for PIC is supported or what is the default behavior (i.e. PIC or no PIC).
CMake will generate PIC option only if property POSITION_INDEPENDENT_CODE is TRUE. So I strongly recommend to not set any PIC compiler option by default.
For PIE, you can specify both options (if your compiler support them), through variables CMAKE_<LANG>_LINK_OPTIONS_PIE and CMAKE_<LANG>_LINK_OPTIONS_NO_PIE, for activating or deactivating PIE generation. The CMake generation rule is the following, depending on target property value:
POSITION_INDEPENDENT_CODE not set, no PIE flags generated.
POSITION_INDEPENDENT_CODE is set to TRUE, variable ...PIE is used.
POSITION_INDEPENDENT_CODE is set to FALSE, variable ...NO_PIE is used.
So to sum up: without any POSITION_INDEPENDENT_CODE target property, use defaults compiler/linker behavior.
Set the property (TRUE or FALSE) only when you want a specific behavior. PIE or not PIE executable.
IIUC the problem is that the ISPC toolchain and the GNU toolchain on a given system do not necessarily agree about the default w.r.t. PIC. If no flags are used at all the resulting binary may not link correctly. For ISPC we need to know whether the C/C++ compiler enables PIC/PIE by default in order to match it via explicit flags. Therefore we need a way to detect, perhaps via try_compile, what the C compile does.
I gave CheckSymbolExists a try, to check if __PIC__ or __PIE__ are defined. Those are indeed defined on my system by default, and are not if I use -fno-pic (though the program won’t compile since the linker still tries to generate a PIE).
This seems like a decent thing to do when GCC or clang is detected, but I’m not sure if it’s the right way to do this? Could other compilers show the same issue ? I think it is more a linker issue than compiler, but I’m not sure how one could check the linker defaults except by using try_compile with -fno-pic.
I doubt the compiler would be configured without pic/pie if the linker defaults to pie, but I’m not an expert in the domain.
You can generate an executable with option -fpie or option -fno-pie with files compiled in PIC mode.
To generate an executable with option -fpie, it is required to compile in PIC mode.
So, for your compiler, I think defining variables CMAKE_<LANG>_COMPILE_OPTIONS_PIC, CMAKE_<LANG>_LINK_OPTIONS_PIE and CMAKE_<LANG>_LINK_OPTIONS_NO_PIE will be enough. And manage PIE link step by using CheckPIESupported module.
So, for your compiler, I think defining variables CMAKE_COMPILE_OPTIONS_PIC, CMAKELINK_OPTIONS_PIE and CMAKE_LINK_OPTIONS_NO_PIE will be enough.
Not really, it is kind of the behaviour I have right now. As @brad.king mentioned, the issue is that the linker coming with my linux distribution defaults to using PIE (and GCC compiles with PIC/PIE by default too) without us having to give it any flag.
ISPC, which produces object files, has a different default which is not to enable PIC.
So right now, everything compiles, but when I try to link I’m getting an error because the linker expects both GCC and ISPC to produce objects that have position independent code.
So right now, by default (regardless of CMAKE_<LANG>_LINK_OPTIONS_PIE / CMAKE_<LANG>_LINK_OPTIONS_NO_PIE) it will not build correctly on this system, due to GNU tools and ISPC having different defaults.
That’s what I’m trying to fix.
If CMAKE_<lang>_LINK_OPTIONS_NO_PIE is used by default (when CMAKE_POSITION_INDEPENDENT_CODE is not set) then what I would need is for CMAKE_C_LINK_OPTIONS_NO_PIE and CMAKE_CXX_LINK_OPTIONS_NO_PIE to actually use the flag -fno-pie for GCC/clang instead of nothing?
No. CMAKE_<lang>_LINK_OPTIONS_NO_PIE is not used by default. If POSITION_INDEPENDENT_CODE is not set (TRUE or FALSE), no flags are applied to link step.
PIE flag (-fpie or -fno_pie) is applied only if property POSITION_INDEPENDENT_CODE is set (‘TRUE’ or ‘FALSE’).
To ensure consistency for the link step, define the variable CMAKE_POSITION_INDEPENDENT_CODE and include module CheckPIESupport in the root CMakelist.txt and call check_pie_supported().
variable CMAKE_POSITION_INDEPENDENT_CODE can take TRUE or FALSE:
set to FALSE: gcc and ispc will compile in their default mode (PIC for gcc and NO_PIC for ispc) but the linker will specify -fno_pie so link will succeed.
set to TRUE: gcc and ispc will compile with option -fPIC and the linker will use option -fpie and the link will succeed.