From 9783f6f2f67f9472fa428b984e4e045ee7b64f78 Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Wed, 10 Dec 2025 23:11:26 +0800 Subject: [PATCH] c++/modules: #include -> import Since the standard library doesn't preclude an #include of a standard library header from bringing in declarations from other headers, we can translate an #include of any of the importable headers as an import of . To reduce the amount of C++ standard knowledge encoded in libcpp, I extend the translate_include callback to allow it to suggest an alternate header to try translating. It's a bit awkward to bounce back and forth, but this seems like the right division of responsibilities. libcpp/ChangeLog: * include/cpplib.h (struct cpp_callbacks): Replace 'path' parameter with file, angle_brackets, and alternate name. (cpp_get_name): Declare. * files.cc (cpp_get_name): New. (_cpp_stack_include, _cpp_post_stack_file, _cpp_stack_file) (_cpp_stack_translated_file): Refactor, try alternate file. gcc/cp/ChangeLog: * module.cc (maybe_translate_include): Suggest as an alternate for importable standard library headers. (importable_headers, is_importable_header): New. gcc/ChangeLog: * doc/invoke.texi (C++ Modules): Remove standard library header units from missing pieces, mention importable header redirection. gcc/testsuite/ChangeLog: * g++.dg/modules/compile-std1.C: Test translation. --- gcc/cp/module.cc | 81 ++++++++- gcc/doc/invoke.texi | 15 +- gcc/testsuite/g++.dg/modules/compile-std1.C | 4 +- libcpp/files.cc | 179 ++++++++++++-------- libcpp/include/cpplib.h | 3 +- 5 files changed, 199 insertions(+), 83 deletions(-) diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index 5c70e9bb469..7899fac8a2d 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -22541,11 +22541,66 @@ void module_state::set_filename (const Cody::Packet &packet) } } +/* The list of importable headers from C++ Table 24. */ + +static const char * +importable_headers[] = + { + "algorithm", "any", "array", "atomic", + "barrier", "bit", "bitset", + "charconv", "chrono", "compare", "complex", "concepts", + "condition_variable", "contracts", "coroutine", + "debugging", "deque", + "exception", "execution", "expected", + "filesystem", "flat_map", "flat_set", "format", "forward_list", + "fstream", "functional", "future", + "generator", + "hazard_pointer", "hive", + "initializer_list", "inplace_vector", "iomanip", "ios", "iosfwd", + "iostream", "istream", "iterator", + "latch", "limits", "linalg", "list", "locale", + "map", "mdspan", "memory", "memory_resource", "meta", "mutex", + "new", "numbers", "numeric", + "optional", "ostream", + "print", + "queue", + "random", "ranges", "ratio", "rcu", "regex", + "scoped_allocator", "semaphore", "set", "shared_mutex", "simd", + "source_location", "span", "spanstream", "sstream", "stack", "stacktrace", + "stdexcept", "stdfloat", "stop_token", "streambuf", "string", + "string_view", "syncstream", "system_error", + "text_encoding", "thread", "tuple", "type_traits", "typeindex", "typeinfo", + "unordered_map", "unordered_set", + "utility", + "valarray", "variant", "vector", "version" + }; + +/* True iff is listed as an importable standard header. */ + +static bool +is_importable_header (const char *name) +{ + unsigned lo = 0; + unsigned hi = ARRAY_SIZE (importable_headers); + while (hi > lo) + { + unsigned mid = (lo + hi)/2; + int cmp = strcmp (name, importable_headers[mid]); + if (cmp > 0) + lo = mid + 1; + else if (cmp < 0) + hi = mid; + else + return true; + } + return false; +} + /* Figure out whether to treat HEADER as an include or an import. */ static char * maybe_translate_include (cpp_reader *reader, line_maps *lmaps, location_t loc, - const char *path) + _cpp_file *file, bool angle, const char **alternate) { if (!modules_p ()) { @@ -22554,6 +22609,8 @@ maybe_translate_include (cpp_reader *reader, line_maps *lmaps, location_t loc, return nullptr; } + const char *path = _cpp_get_file_path (file); + dump.push (NULL); dump () && dump ("Checking include translation '%s'", path); @@ -22599,6 +22656,28 @@ maybe_translate_include (cpp_reader *reader, line_maps *lmaps, location_t loc, if (!strcmp ((*note_includes)[ix], path)) note = true; + /* Maybe try importing a different header instead. */ + if (alternate && translate == xlate_kind::unknown) + { + const char *fname = _cpp_get_file_name (file); + /* Redirect importable to . */ + /* ??? Generalize to use a .json. */ + expanded_location eloc = expand_location (loc); + if (angle && is_importable_header (fname) + /* Exclude which often goes with import std. */ + && strcmp (fname, "version") != 0 + /* Don't redirect #includes between headers under the same include + path directory (i.e. between library headers); if the import + brings in the current file we then get redefinition errors. */ + && !strstr (eloc.file, _cpp_get_file_dir (file)->name) + /* ??? These are needed when running a toolchain from the build + directory, because libsupc++ headers aren't linked into + libstdc++-v3/include with the other headers. */ + && !strstr (eloc.file, "libstdc++-v3/include") + && !strstr (eloc.file, "libsupc++")) + *alternate = "bits/stdc++.h"; + } + if (note) inform (loc, translate == xlate_kind::import ? G_("include %qs translated to import") diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 813403a9733..8db0aa0ceb7 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -38780,13 +38780,6 @@ reverse is not implemented---textually redefining an entity that has been defined in an imported header-unit. A redefinition error is emitted. -@item Standard Library Header Units -The Standard Library is not provided as importable header units. If -you want to import such units, you must explicitly build them first. -If you do not do this with care, you may have multiple declarations, -which the module machinery must merge---compiler resource usage can be -affected by how you partition header files into header units. - @end table Modular compilation is @emph{not} enabled with just the @@ -38849,6 +38842,14 @@ and any standard library #includes in mycode.C will be skipped, because the import brought in the whole library. This can be a simple way to use modules to speed up compilation without any code changes. +But for the standard library in particular this is unnecessary: if a +header unit has been built for the libstdc++ @samp{bits/stdc++.h} +header, the compiler will translate an @samp{#include} of any +importable standard library header into an import of that header unit, +speeding up compilation without needing to specify @samp{-include}. +Note that the @samp{bits/stdc++.h} header unit is also built by the +@option{--compile-std-module} option. + The @option{-fmodule-only} option disables generation of the associated object file for compiling a module interface. Only the CMI is generated. This option is implied when using the diff --git a/gcc/testsuite/g++.dg/modules/compile-std1.C b/gcc/testsuite/g++.dg/modules/compile-std1.C index a03a87569a8..1de9ea78976 100644 --- a/gcc/testsuite/g++.dg/modules/compile-std1.C +++ b/gcc/testsuite/g++.dg/modules/compile-std1.C @@ -1,12 +1,14 @@ // { dg-additional-options "-fmodules --compile-std-module -g -O" } +// { dg-additional-options "-flang-info-include-translate" } // { dg-do compile { target c++20 } } // { dg-module-cmi std } // { dg-module-cmi std.compat } // { dg-module-cmi } -import ; import std; import std.compat; +#include // { dg-message "translated to import" } +import ; void f() { diff --git a/libcpp/files.cc b/libcpp/files.cc index f8b33129486..dd65848de7c 100644 --- a/libcpp/files.cc +++ b/libcpp/files.cc @@ -211,6 +211,7 @@ static bool validate_pch (cpp_reader *, _cpp_file *file, const char *pchname); static int pchf_save_compare (const void *e1, const void *e2); static int pchf_compare (const void *d_p, const void *e_p); static bool check_file_against_entries (cpp_reader *, _cpp_file *, bool); +static void _cpp_post_stack_file (cpp_reader *, _cpp_file *, include_type, bool); /* Given a filename in FILE->PATH, with the empty string interpreted as , open it. @@ -954,88 +955,92 @@ bool _cpp_stack_file (cpp_reader *pfile, _cpp_file *file, include_type type, location_t loc) { - if (is_known_idempotent_file (pfile, file, type == IT_IMPORT)) + int sysp = 0; + + /* Not a header unit, and we know it. */ + file->header_unit = -1; + + if (!read_file (pfile, file, loc)) return false; - int sysp = 0; - char *buf = nullptr; + if (!has_unique_contents (pfile, file, type == IT_IMPORT, loc)) + return false; - /* Check C++ module include translation. */ - if (!file->header_unit && type < IT_HEADER_HWM - /* Do not include translate include-next. */ - && type != IT_INCLUDE_NEXT - && pfile->cb.translate_include) - buf = (pfile->cb.translate_include - (pfile, pfile->line_table, loc, file->path)); + if (pfile->buffer && file->dir) + sysp = MAX (pfile->buffer->sysp, file->dir->sysp); + + /* Add the file to the dependencies on its first inclusion. */ + if (CPP_OPTION (pfile, deps.style) > (sysp != 0) + && !file->stack_count + && file->path[0] + && !(pfile->main_file == file + && CPP_OPTION (pfile, deps.ignore_main_file))) + deps_add_dep (pfile->deps, file->path); + + /* Clear buffer_valid since _cpp_clean_line messes it up. */ + file->buffer_valid = false; + file->stack_count++; + + /* Stack the buffer. */ + cpp_buffer *buffer + = cpp_push_buffer (pfile, file->buffer, file->st.st_size, + CPP_OPTION (pfile, preprocessed) + && !CPP_OPTION (pfile, directives_only)); + buffer->file = file; + buffer->sysp = sysp; + buffer->to_free = file->buffer_start; - if (buf) - { - /* We don't increment the line number at the end of a buffer, - because we don't usually need that location (we're popping an - include file). However in this case we do want to do the - increment. So push a writable buffer of two newlines to acheive - that. (We also need an extra newline, so this looks like a regular - file, which we do that to to make sure we don't fall off the end in the - middle of a line. */ - if (type != IT_CMDLINE) - { - static uchar newlines[] = "\n\n\n"; - cpp_push_buffer (pfile, newlines, 2, true); - } + /* Initialize controlling macro state. */ + pfile->mi_valid = true; + pfile->mi_cmacro = 0; - size_t len = strlen (buf); - buf[len] = '\n'; /* See above */ - cpp_buffer *buffer - = cpp_push_buffer (pfile, reinterpret_cast (buf), - len, true); - buffer->to_free = buffer->buf; - if (type == IT_CMDLINE) - /* Tell _cpp_pop_buffer to change files. */ - buffer->file = file; + _cpp_post_stack_file (pfile, file, type, sysp); + return true; +} - file->header_unit = +1; - _cpp_mark_file_once_only (pfile, file); - } - else - { - /* Not a header unit, and we know it. */ - file->header_unit = -1; +/* Like _cpp_stack_file, but for a file that's been replaced by the contents of + BUF. Used for C++ modules include -> import translation. */ - if (!read_file (pfile, file, loc)) - return false; +static bool +_cpp_stack_translated_file (cpp_reader *pfile, _cpp_file *file, + char *buf, include_type type) +{ + /* We don't increment the line number at the end of a buffer, + because we don't usually need that location (we're popping an + include file). However in this case we do want to do the + increment. So push a writable buffer of two newlines to acheive + that. (We also need an extra newline, so this looks like a regular + file, which we do that to to make sure we don't fall off the end in the + middle of a line. */ + if (type != IT_CMDLINE) + { + static uchar newlines[] = "\n\n\n"; + cpp_push_buffer (pfile, newlines, 2, true); + } - if (!has_unique_contents (pfile, file, type == IT_IMPORT, loc)) - return false; + size_t len = strlen (buf); + buf[len] = '\n'; /* See above */ + cpp_buffer *buffer + = cpp_push_buffer (pfile, reinterpret_cast (buf), + len, true); + buffer->to_free = buffer->buf; + if (type == IT_CMDLINE) + /* Tell _cpp_pop_buffer to change files. */ + buffer->file = file; - if (pfile->buffer && file->dir) - sysp = MAX (pfile->buffer->sysp, file->dir->sysp); + file->header_unit = +1; + _cpp_mark_file_once_only (pfile, file); - /* Add the file to the dependencies on its first inclusion. */ - if (CPP_OPTION (pfile, deps.style) > (sysp != 0) - && !file->stack_count - && file->path[0] - && !(pfile->main_file == file - && CPP_OPTION (pfile, deps.ignore_main_file))) - deps_add_dep (pfile->deps, file->path); + _cpp_post_stack_file (pfile, file, type, false); + return true; +} - /* Clear buffer_valid since _cpp_clean_line messes it up. */ - file->buffer_valid = false; - file->stack_count++; - - /* Stack the buffer. */ - cpp_buffer *buffer - = cpp_push_buffer (pfile, file->buffer, file->st.st_size, - CPP_OPTION (pfile, preprocessed) - && !CPP_OPTION (pfile, directives_only)); - buffer->file = file; - buffer->sysp = sysp; - buffer->to_free = file->buffer_start; - - /* Initialize controlling macro state. */ - pfile->mi_valid = true; - pfile->mi_cmacro = 0; - } +/* The common epilogue of _cpp_stack_file and _cpp_stack_translated_file. */ +static void +_cpp_post_stack_file (cpp_reader *pfile, _cpp_file *file, include_type type, + bool sysp) +{ /* In the case of a normal #include, we're now at the start of the line *following* the #include. A separate location_t for this location makes no sense, until we do the LC_LEAVE. @@ -1070,8 +1075,6 @@ _cpp_stack_file (cpp_reader *pfile, _cpp_file *file, include_type type, linenum_type line = SOURCE_LINE (map, pfile->line_table->highest_line); linemap_line_start (pfile->line_table, line - 1, 0); } - - return true; } /* Mark FILE to be included once only. */ @@ -1171,7 +1174,37 @@ _cpp_stack_include (cpp_reader *pfile, const char *fname, int angle_brackets, if (type == IT_DEFAULT && file == NULL) return false; - return _cpp_stack_file (pfile, file, type, loc); + if (is_known_idempotent_file (pfile, file, type == IT_IMPORT)) + return false; + + /* Check C++ module include translation. */ + char *buf = nullptr; + if (!file->header_unit && type < IT_DEFAULT + /* Do not include translate include-next. */ + && type != IT_INCLUDE_NEXT + && pfile->cb.translate_include) + { + const char *aname = nullptr; + buf = (pfile->cb.translate_include + (pfile, pfile->line_table, loc, file, + angle_brackets, &aname)); + if (!buf && aname) + { + _cpp_file *afile = _cpp_find_file (pfile, aname, dir, angle_brackets, + _cpp_FFK_NORMAL, loc); + if (afile && !afile->header_unit) + buf = (pfile->cb.translate_include + (pfile, pfile->line_table, loc, + afile, angle_brackets, nullptr)); + if (buf) + file = afile; + } + } + + if (buf) + return _cpp_stack_translated_file (pfile, file, buf, type); + else + return _cpp_stack_file (pfile, file, type, loc); } /* NAME is a header file name, find the _cpp_file, if any. */ diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h index 16f030c82f3..65e1bc6aae0 100644 --- a/libcpp/include/cpplib.h +++ b/libcpp/include/cpplib.h @@ -860,7 +860,8 @@ struct cpp_callbacks /* Maybe translate a #include into something else. Return a cpp_buffer containing the translation if translating. */ char *(*translate_include) (cpp_reader *, line_maps *, location_t, - const char *path); + _cpp_file *file, bool angle_brackets, + const char **alternate); }; #ifdef VMS -- 2.47.3