Skip to content

Commit 97647c1

Browse files
Teemperorvgvassilev
authored andcommitted
[cxxmodules] Start generating C++ modules in rootcling.
This patch enables C++ modules support in rootcling. We now add the correct cling flags that will trigger the modules setup in the CIFactory. We also start reusing the normal clang code for generating modules as far as possible. For this we had to hack around a few bugs in ROOT like the fact that we actually never call many of the interpreter destructors (or if we do, we don't call the in a consistent way between rootcling and rootcling_stage1), mainly manually calling the end of the translation unit.
1 parent c8e5c50 commit 97647c1

File tree

1 file changed

+125
-53
lines changed

1 file changed

+125
-53
lines changed

core/dictgen/src/rootcling_impl.cxx

Lines changed: 125 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,6 +2235,64 @@ static bool GenerateAllDict(TModuleGenerator &modGen, clang::CompilerInstance *c
22352235
return WriteAST(modGen.GetModuleFileName(), compilerInstance, iSysRoot);
22362236
}
22372237

2238+
static void
2239+
foreachHeaderInModule(const clang::Module &module, const std::function<void(const clang::Module::Header &)> &closure)
2240+
{
2241+
// Iterates over all headers in a module and calls the closure on each.
2242+
2243+
// FIXME: We currently have to hardcode '4' to do this. Maybe we
2244+
// will have a nicer way to do this in the future.
2245+
// NOTE: This is on purpose '4', not '5' which is the size of the
2246+
// vector. The last element is the list of excluded headers which we
2247+
// obviously don't want to check here.
2248+
const std::size_t publicHeaderIndex = 4;
2249+
2250+
// Integrity check in case this array changes its size at some point.
2251+
const std::size_t maxArrayLength = ((sizeof module.Headers) / (sizeof *module.Headers));
2252+
static_assert(publicHeaderIndex + 1 == maxArrayLength,
2253+
"'Headers' has changed it's size, we need to update publicHeaderIndex");
2254+
2255+
for (std::size_t i = 0; i < publicHeaderIndex; i++) {
2256+
auto &headerList = module.Headers[i];
2257+
for (const clang::Module::Header &moduleHeader : headerList) {
2258+
closure(moduleHeader);
2259+
}
2260+
}
2261+
}
2262+
2263+
////////////////////////////////////////////////////////////////////////////////
2264+
/// Includes all headers in the given module from this interpreter.
2265+
static void IncludeModuleHeaders(TModuleGenerator &modGen, clang::Module *module, cling::Interpreter &interpreter)
2266+
{
2267+
// Make a list of modules and submodules that we can check for headers.
2268+
// We use a SetVector to prevent an infinite loop in unlikely case the
2269+
// modules somehow are messed up and don't form a tree...
2270+
llvm::SetVector<clang::Module *> modules;
2271+
modules.insert(module);
2272+
for (size_t i = 0; i < modules.size(); ++i) {
2273+
clang::Module *M = modules[i];
2274+
for (clang::Module *subModule : M->submodules())
2275+
modules.insert(subModule);
2276+
}
2277+
// List of includes the interpreter has to parse.
2278+
std::stringstream includes;
2279+
// Now we include all header files from the previously collected modules.
2280+
// FIXME: This might hide that we have a missing include if one of the
2281+
// previous headers already includes this. We should reuse the clang way
2282+
// of doing this and 'import' each header in its own submodule, then let
2283+
// the visibility of the decls handle this situation nicely.
2284+
for (clang::Module *module : modules) {
2285+
foreachHeaderInModule(*module, [&includes](const clang::Module::Header &h) {
2286+
includes << "#include \"" << h.NameAsWritten << "\"\n";
2287+
});
2288+
}
2289+
std::string includeListStr = includes.str();
2290+
auto result = interpreter.declare(includeListStr);
2291+
if (result != cling::Interpreter::CompilationResult::kSuccess) {
2292+
ROOT::TMetaUtils::Error("IncludeModuleHeaders", "Couldn't include headers:\n%s!\n", includeListStr.c_str());
2293+
}
2294+
}
2295+
22382296
////////////////////////////////////////////////////////////////////////////////
22392297
/// Returns true iff a given module (and its submodules) contains all headers
22402298
/// needed by the given ModuleGenerator.
@@ -2256,18 +2314,8 @@ static bool ModuleContainsHeaders(TModuleGenerator &modGen, clang::Module *modul
22562314
// Now we collect all header files from the previously collected modules.
22572315
std::set<std::string> moduleHeaders;
22582316
for (clang::Module *module : modules) {
2259-
// Iterate over all header types in a module.
2260-
// FIXME: We currently have to hardcode '4' to do this. Maybe we
2261-
// will have a nicer way to do this in the future.
2262-
// NOTE: This is on purpose '4', not '5' which is the size of the
2263-
// vector. The last element is the list of excluded headers which we
2264-
// obviously don't want to check here.
2265-
for (int i = 0; i < 4; i++) {
2266-
auto &headerList = module->Headers[i];
2267-
for (const clang::Module::Header &moduleHeader : headerList) {
2268-
moduleHeaders.insert(moduleHeader.NameAsWritten);
2269-
}
2270-
}
2317+
foreachHeaderInModule(
2318+
*module, [&moduleHeaders](const clang::Module::Header &h) { moduleHeaders.insert(h.NameAsWritten); });
22712319
}
22722320

22732321
// Go through the list of headers that are required by the ModuleGenerator
@@ -2286,48 +2334,15 @@ static bool ModuleContainsHeaders(TModuleGenerator &modGen, clang::Module *modul
22862334
////////////////////////////////////////////////////////////////////////////////
22872335
/// Generates a module from the given ModuleGenerator and CompilerInstance.
22882336
/// Returns true iff the PCM was succesfully generated.
2289-
static bool GenerateModule(TModuleGenerator &modGen, clang::CompilerInstance *CI)
2337+
static bool GenerateModule(TModuleGenerator &modGen, const std::string &resourceDir, cling::Interpreter &interpreter,
2338+
StringRef LinkdefPath, const std::string &moduleName)
22902339
{
2291-
assert(!modGen.IsPCH() && "modGen must not be in PCH mode");
2292-
2293-
std::string outputFile = modGen.GetModuleFileName();
2294-
std::string includeDir = gDriverConfig->fTROOT__GetIncludeDir();
2340+
clang::CompilerInstance *CI = interpreter.getCI();
22952341
clang::HeaderSearch &headerSearch = CI->getPreprocessor().getHeaderSearchInfo();
2296-
auto &fileMgr = headerSearch.getFileMgr();
2297-
2298-
// Load the modulemap from the ROOT include directory.
2299-
clang::ModuleMap &moduleMap = headerSearch.getModuleMap();
2300-
std::string moduleMapPath = includeDir + "/module.modulemap";
2301-
auto moduleFile = fileMgr.getFile(moduleMapPath);
2302-
2303-
// Check if we actually found the modulemap file...
2304-
if (!moduleFile) {
2305-
ROOT::TMetaUtils::Error("GenerateModule", "Couldn't find ROOT modulemap in"
2306-
" %s! Ensure that cxxmodules=On is set in CMake.\n",
2307-
moduleMapPath.c_str());
2308-
return false;
2309-
}
2310-
2311-
moduleMap.parseModuleMapFile(moduleFile, false, fileMgr.getDirectory(includeDir));
2312-
2313-
// Try to get the module name in the modulemap based on the filepath.
2314-
std::string moduleName = llvm::sys::path::filename(outputFile);
2315-
// For module "libCore.so" we have the file name "libCore_rdict.pcm".
2316-
// We replace this suffix with ".so" to get the name in the modulefile.
2317-
if (StringRef(moduleName).endswith("_rdict.pcm")) {
2318-
auto lengthWithoutSuffix = moduleName.size() - strlen("_rdict.pcm");
2319-
moduleName = moduleName.substr(0, lengthWithoutSuffix) + ".so";
2320-
}
2342+
headerSearch.loadTopLevelSystemModules();
23212343

23222344
// Actually lookup the module on the computed module name.
2323-
clang::Module *module = moduleMap.findModule(moduleName);
2324-
2325-
// Inform the user and abort if we can't find a module with a given name.
2326-
if (!module) {
2327-
ROOT::TMetaUtils::Error("GenerateModule", "Couldn't find module with name '%s' in modulemap!\n",
2328-
moduleName.c_str());
2329-
return false;
2330-
}
2345+
clang::Module *module = headerSearch.lookupModule(StringRef(moduleName));
23312346

23322347
// Check if the loaded module covers all headers that were specified
23332348
// by the user on the command line. This is an integrity check to
@@ -2345,7 +2360,15 @@ static bool GenerateModule(TModuleGenerator &modGen, clang::CompilerInstance *CI
23452360
ROOT::TMetaUtils::Warning("GenerateModule", warningMessage.c_str());
23462361
}
23472362

2348-
return WriteAST(outputFile, CI, "", module);
2363+
// Inform the user and abort if we can't find a module with a given name.
2364+
if (!module) {
2365+
ROOT::TMetaUtils::Error("GenerateModule", "Couldn't find module with name '%s' in modulemap!\n",
2366+
moduleName.c_str());
2367+
return false;
2368+
}
2369+
2370+
IncludeModuleHeaders(modGen, module, interpreter);
2371+
return true;
23492372
}
23502373

23512374
////////////////////////////////////////////////////////////////////////////////
@@ -4236,9 +4259,48 @@ int RootClingMain(int argc,
42364259

42374260
ROOT::TMetaUtils::SetPathsForRelocatability(clingArgs);
42384261

4262+
// FIXME: This line is from TModuleGenerator, but we can't reuse this code
4263+
// at this point because TModuleGenerator needs a CompilerInstance (and we
4264+
// currently create the arguments for creating said CompilerInstance).
4265+
bool isPCH = (dictpathname == "allDict.cxx");
4266+
std::string outputFile;
4267+
// Data is in 'outputFile', therefore in the same scope.
4268+
StringRef moduleName;
4269+
// Adding -fmodules to the args will break lexing with __CINT__ defined,
4270+
// and we actually do lex with __CINT__ and reuse this variable later,
4271+
// we have to copy it now.
4272+
auto clingArgsInterpreter = clingArgs;
4273+
if (!isPCH && getenv("ROOT_MODULES")) {
4274+
// We just pass -fmodules, the CIFactory will do the rest and configure
4275+
// clang correctly once it sees this flag.
4276+
clingArgsInterpreter.push_back("-fmodules");
4277+
4278+
// Specify the module name that we can lookup the module in the modulemap.
4279+
outputFile = llvm::sys::path::stem(sharedLibraryPathName).str();
4280+
// Try to get the module name in the modulemap based on the filepath.
4281+
moduleName = llvm::sys::path::filename(outputFile);
4282+
moduleName.consume_front("lib");
4283+
moduleName.consume_back("_rdict.pcm");
4284+
4285+
clingArgsInterpreter.push_back("-fmodule-name");
4286+
clingArgsInterpreter.push_back(moduleName.str());
4287+
4288+
// Set the C++ modules output directory to the directory where we generate
4289+
// the shared library.
4290+
clingArgsInterpreter.push_back("-fmodules-cache-path=" +
4291+
llvm::sys::path::parent_path(sharedLibraryPathName).str());
4292+
4293+
// If the user has passed ROOT_MODULES with the value 'DEBUG', then we
4294+
// enable remarks in clang for building on-demand modules. This is useful
4295+
// to figure out when and why on-demand modules are built by clang.
4296+
if (llvm::StringRef(getenv("ROOT_MODULES")) == "DEBUG") {
4297+
clingArgsInterpreter.push_back("-Rmodule-build");
4298+
}
4299+
}
4300+
42394301
// Convert arguments to a C array and check if they are sane
42404302
std::vector<const char *> clingArgsC;
4241-
for (auto const & clingArg : clingArgs) {
4303+
for (auto const &clingArg : clingArgsInterpreter) {
42424304
if (!IsCorrectClingArgument(clingArg)){
42434305
std::cerr << "Argument \""<< clingArg << "\" is not a supported cling argument. "
42444306
<< "This could be mistyped rootcling argument. Please check the commandline.\n";
@@ -4879,7 +4941,8 @@ int RootClingMain(int argc,
48794941
if (modGen.IsPCH()) {
48804942
if (!GenerateAllDict(modGen, CI, currentDirectory)) return 1;
48814943
} else if (getenv("ROOT_MODULES")) {
4882-
if (!GenerateModule(modGen, CI)) return 1;
4944+
if (!GenerateModule(modGen, resourceDir, interp, linkdefFilename, moduleName.str()))
4945+
return 1;
48834946
}
48844947
}
48854948
}
@@ -4989,6 +5052,15 @@ int RootClingMain(int argc,
49895052
if (genreflex::verbose)
49905053
tmpCatalog.dump();
49915054

5055+
// Manually call end of translation unit because we never call the
5056+
// appropriate deconstructors in the interpreter. This writes out the C++
5057+
// module file that we currently generate.
5058+
{
5059+
cling::Interpreter::PushTransactionRAII RAII(&interp);
5060+
CI->getSema().getASTConsumer().HandleTranslationUnit(CI->getSema().getASTContext());
5061+
CI->clearOutputFiles(CI->getDiagnostics().hasErrorOccurred());
5062+
}
5063+
49925064
// Before returning, rename the files
49935065
rootclingRetCode += tmpCatalog.commit();
49945066

0 commit comments

Comments
 (0)