Skip to content

Better support building the runtime separately #4743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
project(runtime C ASM)

cmake_minimum_required(VERSION 3.4.3)
project(runtime C ASM)

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR})

Expand Down Expand Up @@ -337,7 +336,7 @@ if(LDC_EXE)
"$<$<BOOL:${SHARED_LIBS_SUPPORTED}>:${MULTILIB_DIR}>"
${conf_path}.in
${conf_path}
DEPENDS ${LDC_EXE} ${LDC_EXE_FULL}
DEPENDS ${LDC_EXE_FULL}
)
add_custom_command(OUTPUT ${install_conf_path} VERBATIM
COMMAND ${PROJECT_PARENT_DIR}/tools/add-multilib-section.sh
Expand All @@ -347,7 +346,7 @@ if(LDC_EXE)
"$<$<BOOL:${SHARED_LIBS_SUPPORTED}>:${MULTILIB_INSTALL_DIR}>"
${install_conf_path}.in
${install_conf_path}
DEPENDS ${LDC_EXE} ${LDC_EXE_FULL}
DEPENDS ${LDC_EXE_FULL}
)
add_custom_target(add-multilib-section ALL
DEPENDS ${conf_path} ${install_conf_path}
Expand Down Expand Up @@ -437,7 +436,9 @@ macro(dc src_files src_basedir d_flags output_basedir emit_bc all_at_once single
list(APPEND dc_flags -flto=thin --output-bc)
endif()

set(dc_deps ${LDC_EXE} ${LDC_EXE_FULL} ${GCCBUILTINS})
# dc_deps can only contain paths, otherwise cmake will ignore the dependency.
# See: https://github.com/ldc-developers/ldc/pull/4743#issuecomment-2323156173
set(dc_deps ${LDC_EXE_FULL} ${GCCBUILTINS})
Copy link
Member

@kinke kinke Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yet we add a add-multilib-section target name 3 lines below. According to

If the argument is the name of a target (created by the add_custom_target(), add_executable(), or add_library() command) a target-level dependency is created to make sure the target is built before any target using this custom command.

this should still make sure the target is created before building any dependent. The (additional) path dependency should take care of the 2nd part wrt. rebuilds IIUC:

Additionally, if the target is an executable or library, a file-level dependency is created to cause the custom command to re-run whenever the target is recompiled.

I guess they actually mean 'if the target is an add_executable or add_library target', so that one has to specify the path to the add_custom_target executable manually for proper rebuilds.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yet we add a add-multilib-section target name 3 lines below. According to

If the argument is the name of a target (created by the add_custom_target(), add_executable(), or add_library() command) a target-level dependency is created to make sure the target is built before any target using this custom command.

this should still make sure the target is created before building any dependent. The (additional) path dependency should take care of the 2nd part wrt. rebuilds IIUC:

From looking at build.ninja, the add-multilib-section target is added as a dependency to the final libraries, like lib64/libdruntime-ldc-shared.so.109.1. I think this is fine, as the libraries are compiled with -conf= so they don't actually need the config file to be updated. In this case I think the weird cmake behavior does what we want: "update the config file before declaring the library built", it just does it a little bit later in the process.

Additionally, if the target is an executable or library, a file-level dependency is created to cause the custom command to re-run whenever the target is recompiled.

I guess they actually mean 'if the target is an add_executable or add_library target', so that one has to specify the path to the add_custom_target executable manually for proper rebuilds.

I'm reading this the other way around:

  • if the target is an executable or library, cmake adds a file-path dependency automatically => we don't need to specify a full path as a dependency.
  • otherwise, cmake does nothing => we need to specify the full path if we need that dependency to be built before this add_custom_command or add_custom_target.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess they actually mean 'if the target is an add_executable or add_library target', so that one has to specify the path to the add_custom_target executable manually for proper rebuilds.

I'm reading this the other way around:

I'm pretty sure the 'target' they refer to is the dependency being added, i.e., LDC_EXE (expands to ldc2) here. Which can either be an add_executable or an add_custom_target, depending on LDC_LINK_MANUALLY. So in case the ldc2 executable is a custom target, we additionally add LDC_EXE_FULL so that the runtimes stuff is rebuilt whenever the compiler is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, I understand it the same way. Maybe I failed to word it right but, in my defense, it is a unintuitive behavior cmake is exhibiting.

... so that the runtimes stuff is rebuilt whenever the compiler is.

  • that the compiler is built, if not present, before the runtime build commands try to invoke it.

Copy link
Member

@kinke kinke Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fully agreed wrt. unintuitive (edit: and bad wording on my part - the compiler file dependency via LDC_EXE_PATH should make sure existing runtime build artifacts get stale as soon as the compiler is rebuilt). Probably related: we had CI issues with the make backend, IIRC some targets not waiting for the prerequisites to be finished. Working fine with ninja though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay after another re-read, I think I got your point now - that the LDC_EXE target name dependency does NOT apply to the custom command getting this dependency directly, but only to the dependents of that custom command. So you're absolutely right, the target-name dependency is insufficient, we need the path dependency to make it apply to the custom command directly, and not just for rebuilds. Thanks for digging!

if(TARGET add-multilib-section)
# Make sure the config files are available before invoking LDC.
set(dc_deps ${dc_deps} add-multilib-section)
Expand Down Expand Up @@ -905,6 +906,7 @@ install(FILES ${GCCBUILTINS} DESTINATION ${INCLUDE_INSTALL_DIR}/ldc)
#
# Test targets.
#
enable_testing()

# Build the "test runner" executables containing the druntime and Phobos unit
# tests. They are invoked with the modules to test later.
Expand Down
4 changes: 3 additions & 1 deletion runtime/DRuntimeIntegrationTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ else()
list(REMOVE_ITEM testnames uuid)
endif()

string(REPLACE ";" " " LDMD_CMD "${LDMD_EXE_FULL} ${D_EXTRA_FLAGS}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your use case would be setting D_EXTRA_FLAGS=-m32, right? Just to understand. This step should probably be skipped on Windows [but that divergence would be a bit ugly IMO], to avoid spaces in the path and spaces as flag separators.

AFAICT, the multilib case would be designed to be used with the MODEL make variable:

MODEL_FLAG:=$(if $(findstring $(MODEL),default),,-m$(MODEL))

The MODEL would make it into DFLAGS:

DFLAGS:=$(MODEL_FLAG) $(PIC) -w -I../../src -I../../import -I$(SRC) -defaultlib=$(if $(findstring ldmd2,$(DMD)),druntime-ldc,) -preview=dip1000 $(if $(findstring $(OS),windows),,-L-lpthread -L-lm $(LINKDL))

and CFLAGS_BASE (which we override for non-MSVC targets via CMake cflags_base though):

CFLAGS_BASE:=$(if $(findstring $(OS),windows),/Wall,$(MODEL_FLAG) $(PIC) -Wall)

How do you currently handle the C side for the separate 32-bit build? Setting a CC env var incl. an -m32 flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your use case would be setting D_EXTRA_FLAGS=-m32, right? Just to understand. This step should probably be skipped on Windows [but that divergence would be a bit ugly IMO], to avoid spaces in the path and spaces as flag separators.

Yes, only -m32 for now. Though it is technically possible, in some future, for this to use other -m<abi> flags for different architectures.

AFAICT, the multilib case would be designed to be used with the MODEL make variable:

MODEL_FLAG:=$(if $(findstring $(MODEL),default),,-m$(MODEL))

The MODEL would make it into DFLAGS:

DFLAGS:=$(MODEL_FLAG) $(PIC) -w -I../../src -I../../import -I$(SRC) -defaultlib=$(if $(findstring ldmd2,$(DMD)),druntime-ldc,) -preview=dip1000 $(if $(findstring $(OS),windows),,-L-lpthread -L-lm $(LINKDL))

and CFLAGS_BASE (which we override for non-MSVC targets via CMake cflags_base though):

CFLAGS_BASE:=$(if $(findstring $(OS),windows),/Wall,$(MODEL_FLAG) $(PIC) -Wall)

How do you currently handle the C side for the separate 32-bit build? Setting a CC env var incl. an -m32 flag?

These variables are set already by the Gentoo helper functions so I didn't have to interact with them but, if I followed the functions right:

Here's what the logs contain:

460: x86_64-pc-linux-gnu-gcc -m32 -mfpmath=sse -Wall -Wl,-rpath,/var/tmp/portage/dev-lang/ldc2-1.40.0_beta3-r1/work/ldc-1.40.0-beta3-src_build-abi_x86_32.x86/lib -L/var/tmp/portage/dev-lang/ldc2-1.40.0_beta3-r1/work/ldc-1.40.0-beta3-src_build-abi_x86_32.x86/lib -O3 -o/var/tmp/portage/dev-lang/ldc2-1.40.0_beta3-r1/work/ldc-1.40.0-beta3-src_build-abi_x86_32.x86/druntime-test-shared-release/linkDR  src/linkDR.c /var/tmp/portage/dev-lang/ldc2-1.40.0_beta3-r1/work/ldc-1.40.0-beta3-src_build-abi_x86_32.x86/lib/libdruntime-ldc-shared.so -ldl  -pthread

So yeah, setting CC seems like what's doing the trick, including for the druntime tests that are called through cmake. Note that there isn't a single -m32 flag, -mfpmath=sse is also present so I would like the solution to allow any combination of flags.

Setting MODEL_FLAG=${D_EXTRA_FLAGS} seems like it would do the trick:

  • allows for arbitrary flags to be passed through
  • doesn't interfere with space-splitting CC

But can you explain something for me? Does gmake on windows not perform space expansion on variables, regardless of where they appear, or is there some special case only for when the variable appears as the first thing in a command. For example:

main: main.cpp
	$(CC) $(CFLAGS) -o main main.cpp

CFLAGS gets split by spaces. CC is also split on linux. Does windows not split CC but splits CFLAGS?

Copy link
Member

@kinke kinke Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I seem to recall/assume (haven't tested it now!) that on Windows, you are supposed to use explicit quotes, something like make CC="my cc.exe". IIRC, make then uses those quotes as-is, so that $(CC) -c gets expanded to "my cc.exe" -c. This should allow adding the D_EXTRA_FLAGS to LDMD_CMD on Windows too. If a poor soul had to split it up into executable and flags, that'd be a PITA of course, as trivial splitting by space wouldn't work of course.

Edit: Tested it; the cmdline needs extra quoting, but the rest holds: make "CC=\"my cc.exe\"".
Edit2: Nope, make CC="my cc.exe" also works (in a cmd.exe shell), surprisingly. But that might be some special make behavior on Windows (wrt. the executable part of the command); make "CC=my cc.exe" also works.
Edit3: With a flag, this would apparently have to be: make "CC=\"my cc.exe\" -abc".
Edit4: Oh god, there's definitely some magic logic somewhere, this really works too: make "CC=my cc -abc". With a C:\Windows\my cc.exe (copy of calc.exe ;)).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing! I don't have a windows machine so I trust you on these.

So, what's the final solution we go with?

  1. allow LDMD_CMD co contain arguments
  2. use MODEL_FLAG to pass D_EXTRA_FLAGS

1 is unintutive on windows because of the comamnd magic, 2 is unintuitive everywhere since the variables don't really seem related.

If the tests are adjusted to also test for 32 bit with -DMULTILIB=ON that would require passing -m32/-m64 somewhere. We could use MODEL here or just override MODEL_FLAG again. I think the final result would look like:

1 + MODEL:

# do_druntime_tests invokes the tests with make MODEL=${1}
do_druntime_tests("")
if(MULTILIB)
  do_druntime_tests("${HOST_BITNESS}")
endif()

1 + MODEL_FLAG:

# do_druntime_tests invokes the tests with make MODEL_FLAG=${1}
do_druntime_tests("")
if(MULTILIB)
  do_druntime_tests("-m${HOST_BITNESS}")
endif()

2 + MODEL_FLAG:

# do_druntime_tests invokes the tests with make MODEL_FLAG="${D_EXTRA_FLAGS} ${1}"
do_druntime_tests("")
if(MULTILIB)
  do_druntime_tests("-m${HOST_BITNESS}")
endif()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to, sometime, change the dmd makefiles to allow taking flags from the command line instead of relying on hacks like using MODEL_FLAG, as Gentoo policy dictates it. If I get to upstream these changes we should be able to do make MY_D_FLAGS=${D_EXTRA_FLAGS} and use either of the model flag logic so maybe it's worth taking this future possibility into consideration.

Copy link
Member

@kinke kinke Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sake of standardization I would recommend that those variables are, instead, named DFLAGS and CFLAGS as that makes their intent very clear and is the standard way of handling flags.

AFAIK, it's common to have such CFLAGS_BASE, in order to only set optional extra/critical flags on demand. Specifying a make variable like this (after make, not as env vars) silently skips all regular assignments of CFLAGS_BASE in common.mak. The full list in CFLAGS etc. can then still be joined together in common.mak, based on (potentially custom) CFLAGS_BASE.

Similarly, common.mak currently also allows one to customize CXXFLAGS_BASE - it just defaults to CFLAGS_BASE in

CXXFLAGS_BASE:=$(CFLAGS_BASE)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if ldc provides simple variables like RT_CFLAGS and RT_LFLAGS to configure its build, on top of working with the flags cmake is configured with, then the easiest way to pass them along to the druntime tests is having the makefile respect variables like DFLAGS_BASE and CFLAGS_BASE just like you suggest.

[The non-super-consistently named CMake variables come from having a fat umbrella CMake build for LDC, where runtime and Phobos are just a subset of all builds. So we ended up with some ugly names for compiler-only D flags, runtime-only D flags etc. etc. They are usually empty, but designed exactly for such target-specific critical flags and/or custom flags.]

In the worst case I would need to add -DRT_CFLAGS=$(get_abi_CFLAGS) which isn't a deal breaker.

The only potential problem I see with you specifying the C flags explicitly this way is that they would be duplicated when building the runtime libraries (due to Gentoo's CMake hook injecting the critical flags), and potentially also for the make integration tests if CC was still set with flags at test-time. The compiler might complain about some options being specified multiple times.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only potential problem I see with you specifying the C flags explicitly this way is that they would be duplicated when building the runtime libraries (due to Gentoo's CMake hook injecting the critical flags), and potentially also for the make integration tests if CC was still set with flags at test-time. The compiler might complain about some options being specified multiple times.

I meant that I would pass-DRT_CFLAGS=-m32 if the cmake code were changed to invoke gmake with CC=${CMAKE_C_COMPILER} to respect the configure-time value. In that case the environment variable is not in effect so $(CC) inside make will be the compiler binary without any arguments so there won't be any duplication with RT_CFLAGS.

Copy link
Member

@kinke kinke Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's the case we can handle. But what about the actual library build?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, in that case the flags would be duplicated. This shouldn't be an issue and, even if it is, I can always just patch the cmake files to use a separate variable for passing abi-related CFLAGS and only respect in when invoking make, like CC="${CMAKE_C_COMPILER} ${GENTOO_ABI_CFLAGS}" since this should work on UNIXes.

This is slightly more work for me but, given the choices:

  1. don't respect ${CMAKE_C_COMPILER} when calling the makefiles and rely on runtime CC
  2. respect ${CMAKE_C_COMPILER}, even if it discards flags

I would prefer for ldc to use 2, because, if it breaks, it would be easier to diagnose even if that entails me possibly carrying one more patch downstream.


foreach(name ${testnames})
foreach(build debug release)
set(druntime_path_build ${druntime_path})
Expand All @@ -72,7 +74,7 @@ foreach(name ${testnames})
)
add_test(NAME ${fullname}
COMMAND ${GNU_MAKE_BIN} -C ${PROJECT_SOURCE_DIR}/druntime/test/${name}
ROOT=${outdir} DMD=${LDMD_EXE_FULL} BUILD=${build}
ROOT=${outdir} DMD=${LDMD_CMD} BUILD=${build}
DRUNTIME=${druntime_path_build} DRUNTIMESO=${shared_druntime_path_build}
SHARED=1 ${cflags_base} ${linkdl}
)
Expand Down