https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127598
Bug ID: 127598
Summary: Forward declared classes / struct, fail with "bad file
data" when further defined with an implementation that
contains a lambda (c++ modules)
Product: gcc
Version: 16.2.1
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: kingdomkind at protonmail dot com
Target Milestone: ---
Created attachment 65680
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65680&action=edit
A patch file I made which works for me BUT DO NOT TRUST! I DO NOT KNOW WHAT I
AM DOING! I AM NOT A COMPILER DEVELOPER! I have tried my best, but I do not
know what I do not know
GCC Version:
gcc (GCC) 16.2.1 20260810
(also tested with git, same issue)
System Type:
x86_64-pc-linux-gnu
(artix linux)
GCC Options built / configured with:
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/16/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../gcc/configure
--enable-languages=ada,c,c++,d,fortran,go,lto,m2,objc,obj-c++,rust,cobol
--enable-bootstrap --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib
--mandir=/usr/share/man --infodir=/usr/share/info
--with-bugurl=https://gitea.artixlinux.org/packages/gcc/issues
--with-build-config=bootstrap-lto --with-gcc-major-version-only
--with-linker-hash-style=gnu --with-system-zlib --enable-cet=auto
--enable-checking=release --enable-clocale=gnu --enable-default-pie
--enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object
--enable-libstdcxx-backtrace --enable-link-serialization=1
--enable-linker-build-id --enable-lto --enable-multilib --enable-plugin
--enable-shared --enable-threads=posix --disable-fixincludes --disable-libssp
--disable-libstdcxx-pch --disable-werror
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 16.2.1 20260810 (GCC)
definition.cppm:
export module a:definition;
export import :forward;
export struct S {
static constexpr auto lambda = []{};
};
forward.cppm:
export module a:forward;
export struct S;
primary.cppm:
export module a;
export import :definition;
Assuming the following are sitting in the same directory:
[repro]$ g++ -std=c++26 -fmodules -c forward.cppm
[repro]$ g++ -std=c++26 -fmodules -c definition.cppm
[repro]$ g++ -std=c++26 -fmodules -c primary.cppm
In module imported at primary.cppm:3:8:
a:definition: error: failed to read compiled module cluster 2: Bad file data
a:definition: note: compiled module file is ‘gcm.cache/a-definition.gcm’
a:definition: error: failed to read compiled module: Bad file data
a:definition: fatal error: returning to the gate for a mechanical issue
compilation terminated.
When you try to forward declare some class / struct, and then in another
partition, actually define it, wherein the definition includes a lambda, the
above error will occur when importing that into another module / partition.
Note that even a lambda within a function (where the definition is written in
the module) will trigger this, as any lambda is unnecessarily serialised into
the CMI file, granted, this is harmless when actual cause of the bad file data
is rectified.
Here is what I believe to be causing the issue, alongside a patch file which I
made which seems to work fine for me. Note, I used LLMs to try and understand
the GCC codebase, and to help make the patch file, I am not a compiler
developer and chances are I do not know what I do not know, therefore take all
this with 2 hands full of salt (although, I did spend over a week trying to
understand a suitable patch).
"GCC reads a cluster from a CMI file, one at a time. A cluster is a section in
the ELF file (i.e. an SCC). GCC parses each declaration in the SCC and does the
following for each. It pushes back the declaration to the back_refs (tag =
insert (decl);), which is a vector of trees, which returns the negative index
as tag. Later, with key_mergeable it then tries to find a declaration to an
existing tree, of which refers to the same entity. If this succeeds, the
back_refs[~tag] is then set to be a pointer to that decl, rather than the one
we just made. We then set is_new = existing == decl, where existing is equal to
the current back_refs[~tag]. Hence, if it is true, it means there was no
matching older decl (it wasn't overwritten in back_refs), and if false, there
was.
the keyed_table->get_or_insert then generates, or gets the keyed table for that
matching declaration, setting existed to true if it was already found. The
keyed table stores, as far as i can tell, associated entities such as lambdas
stored alongside the declaration, since they do not have a name to refer to
explicitly. So things will refer to them as S -> 0, S -> 1, given S is the
declaration, or atleast that is how it appears to me.
By either regard, it assumes that if is_new == existed then, corruption has
occured. However, a declaration can be both new, without the keyed list having
already existed. Eg. a forward declaration wouldn't introduce the keyed list,
but then the 2nd declaration actually defining it would, meaning that is_new ==
false and existed == false, therefore triggering the overrun (error).
Hence, we remove that case from triggering the overrun error.
DECL_MODULE_KEYED_DECLS_P (declaration_that_owns_the_keyed_list) = true; just
means set the flag to say the declaration now has a keyed list.
This patch was required, as it prevented lambdas from being defined in a class,
or function, which was also forward declared. In my case, it was triggered on
having a lambda in a function definition, of which shouldn't even be serialised
into the CMI in the first place. I did modify record_lambda to prevent that,
but I couldn't really figure out how to make it avoid lambdas in function
definitions that should be serialised into the CMI, such as as a return (which
would be fine, since I don't do that anyway, but it's very unideal), hence why
i've just fixed the direct bug, and i'll leave the other one given it took me
over a week to just do 7 lines to try and understand."
The patch:
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 571e7875e..eae038a5d 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -8915,17 +8915,20 @@ trees_in::decl_value ()
auto &set = keyed_table->get_or_insert (STRIP_TEMPLATE (existing),
&existed);
unsigned num = u ();
- if (is_new == existed)
+ if (is_new == true && existed == true)
set_overrun ();
- if (is_new)
+ if (!existed) {
set.reserve (num);
+ tree declaration_that_owns_the_keyed_list = STRIP_TEMPLATE (existing);
+ DECL_MODULE_KEYED_DECLS_P (declaration_that_owns_the_keyed_list) =
true;
+ }
for (unsigned ix = 0; !get_overrun () && ix != num; ix++)
{
tree attached = tree_node ();
dump (dumper::MERGE)
&& dump ("Read %d[%u] %s attached decl %N", tag, ix,
is_new ? "new" : "matched", attached);
- if (is_new)
+ if (!existed)
set.quick_push (attached);
else if (set[ix] != attached)
{
(the repo, https://github.com/kingdomkind/gcc-patched, although I have pasted
it out here verbatim)
I apologise in advance for anything I may have missed, or for incorrect
formatting / bug specification. Thank you for taking the time to read this. I
have also attached the patch file as an attachment, incase that is more
convenient.