ISPC Language support

So small update on linux with Ninja:

I got the following message

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 ?

Something like this may work (untested):

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.”.

Right now the scripts for ispc examples (https://github.com/ispc/ispc/blob/master/examples/cmake/AddISPCExample.cmake#L47) uses uname to determine the architecture. I do not really like this since it makes it harder to support cross compilation.

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

CMAKE_<LANG>_FLAGS_<CONFIG> is normally handled by the generators, so that won’t be a problem if the language were converted to first-class.

For add_custom_command something like this may work:

add_custom_command(...
   COMMAND ... "$<$<CONFIG:Debug>:${CMAKE_ISPC_FLAGS_DEBUG}>"
               "$<$<CONFIG:Release>:${CMAKE_ISPC_FLAGS_RELEASE}>"
               ...
   COMMAND_EXPAND_LISTS
  ...)

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.

FYI, PIE and PIC are partly uncorrelated:

  • 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?

EDIT:

Basicly, the following (pseudocode) won’t work.

$CC main.c -o main.o
ispc code.ispc -o code.o
$LD main.o code.o 

and ouput something along the lines of

/usr/bin/ld: (code.o): relocation R_X86_64_32S against `.rodata' can not be used when making a PIE object; recompile with -fPIC

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.

Ok so that’s what I expected, by default, it will do nothing and let the linker use its default. (which is here, use PIE)

I do understand how to use it from the point of view of the user, but I’m writing a module to share/upstream later on.
I do not have control over CMAKE_POSITION_INDEPENDENT_CODE and I do not want to set it in my module.

Indeed it works if it is either set to TRUE or FALSE, but I don’t think I should enforce that onto the users ?
I mean, we could just say that it’s a problem of configuration on the given system, but I think it’s kind of bad that the user himself has to chime in and set CMAKE_POSITION_INDEPENDENT_CODE manually on such a system.

Ultimately, what I do need is to detect if the linker uses PIE or not by default (by that, I mean without giving it any flag, regardless of CMake variables), which seems to be hard. I suppose the only way to do it is to try_compile with and without ispc’s --pic flag, and see if it really is required or not by default.

@Lectem I don’t expect we will upstream a module-only/custom-command-based implementation of ISPC language support. An upstream implementation will be likely able to access the information it needs in the C++ generator implementations.

I see, but then it means work needs to be done wrt. to generated source files right ?
I don’t think I have enough experience/time with CMake internals to implement this sadly.

Right, that goes back to our earlier discussion in this topic. I don’t expect you to solve those problems or get an upstream-able implementation working. The fully-external approach should work well enough for now pending some separate future dedicated upstream effort.