c++/modules: #include <vector> -> import <bits/stdc++.h>

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.

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.
This commit is contained in:
Jason Merrill
2025-12-10 23:11:26 +08:00
parent 23d71494fe
commit 9783f6f2f6
5 changed files with 204 additions and 88 deletions

View File

@@ -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 <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")

View File

@@ -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

View File

@@ -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()
{

View File

@@ -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. */

View File

@@ -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