On Wed, 10 Dec 2025, Jason Merrill wrote:
> Actually applying this revised version. Tested x86_64-pc-linux-gnu.
>
> -- 8< --
>
> 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
> <bits/stdc++.h>.
>
> 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.
IIUC this will effectively mitigate against PR c++/99000
include-after-import errors? Perhaps the commit should mention this
PR.
>
> 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 <bits/stdc++.h>
> 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 <vector> translation.
> ---
> gcc/doc/invoke.texi | 15 +-
> libcpp/include/cpplib.h | 3 +-
> gcc/cp/module.cc | 81 ++++++++-
> gcc/testsuite/g++.dg/modules/compile-std1.C | 4 +-
> libcpp/files.cc | 189 ++++++++++++--------
> 5 files changed, 204 insertions(+), 88 deletions(-)
>
> 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/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
> 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"
> + };
This array could be const/constexpr?
> +
> +/* True iff <name> 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 <name> to <bits/stdc++.h>. */
> + /* ??? Generalize to use a .json. */
> + expanded_location eloc = expand_location (loc);
> + if (angle && is_importable_header (fname)
> + /* Exclude <version> 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/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 <bits/stdc++.h> }
>
> -import <bits/stdc++.h>;
> import std;
> import std.compat;
> +#include <vector> // { dg-message "translated to import" }
> +import <bits/stdc++.h>;
>
> 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 <stdin>, 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);
>
> - if (buf)
> + /* 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;
> +
> + /* Initialize controlling macro state. */
> + pfile->mi_valid = true;
> + pfile->mi_cmacro = 0;
> +
> + _cpp_post_stack_file (pfile, file, type, sysp);
> + return true;
> +}
> +
> +/* Like _cpp_stack_file, but for a file that's been replaced by the contents
> of
> + BUF. Used for C++ modules include -> import translation. */
> +
> +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)
> {
> - /* 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);
> - }
> -
> - size_t len = strlen (buf);
> - buf[len] = '\n'; /* See above */
> - cpp_buffer *buffer
> - = cpp_push_buffer (pfile, reinterpret_cast<unsigned char *> (buf),
> - len, true);
> - buffer->to_free = buffer->buf;
> - if (type == IT_CMDLINE)
> - /* Tell _cpp_pop_buffer to change files. */
> - buffer->file = file;
> -
> - file->header_unit = +1;
> - _cpp_mark_file_once_only (pfile, file);
> - }
> - else
> - {
> - /* Not a header unit, and we know it. */
> - file->header_unit = -1;
> -
> - if (!read_file (pfile, file, loc))
> - return false;
> -
> - if (!has_unique_contents (pfile, file, type == IT_IMPORT, loc))
> - return false;
> -
> - 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;
> -
> - /* Initialize controlling macro state. */
> - pfile->mi_valid = true;
> - pfile->mi_cmacro = 0;
> + static uchar newlines[] = "\n\n\n";
> + cpp_push_buffer (pfile, newlines, 2, true);
> }
>
> + size_t len = strlen (buf);
> + buf[len] = '\n'; /* See above */
> + cpp_buffer *buffer
> + = cpp_push_buffer (pfile, reinterpret_cast<unsigned char *> (buf),
> + len, true);
> + buffer->to_free = buffer->buf;
> + if (type == IT_CMDLINE)
> + /* Tell _cpp_pop_buffer to change files. */
> + buffer->file = file;
> +
> + file->header_unit = +1;
> + _cpp_mark_file_once_only (pfile, file);
> +
> + _cpp_post_stack_file (pfile, file, type, false);
> + return true;
> +}
> +
> +/* 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. */
>
> base-commit: 23d71494fe0a6244b870db5a4f879b528903f52c
> --
> 2.52.0
>
>