Bug 264730 - clang++ -fmodules fails when using stdout from cstdio: error: declaration of '__stdoutp' must be imported from module 'std.iosfwd' before it is required
Summary: clang++ -fmodules fails when using stdout from cstdio: error: declaration of ...
Status: New
Alias: None
Product: Base System
Classification: Unclassified
Component: bin (show other bugs)
Version: 13.1-RELEASE
Hardware: Any Any
: --- Affects Only Me
Assignee: freebsd-toolchain (Nobody)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-06-17 07:08 UTC by Michał Górny
Modified: 2022-06-25 07:45 UTC (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Michał Górny 2022-06-17 07:08:27 UTC
While trying to figure out a build failure for an LLDB test, I've noticed a problem with clang++ on FreeBSD.  The minimal reproducer is:

```
#include <cstdio>

int main() {
  std::FILE* f = stdout;
  return 0;
}
```

Building with -fmodules fails:

```
$ clang++ -fmodules /tmp/test.cxx 
/tmp/test.cxx:4:18: error: declaration of '__stdoutp' must be imported from module 'std.iosfwd' before it is required
  std::FILE* f = stdout;
                 ^
/usr/include/stdio.h:241:16: note: expanded from macro 'stdout'
#define stdout  __stdoutp
                ^
/usr/include/wchar.h:184:14: note: declaration here is not visible
extern FILE *__stdoutp;
             ^
1 error generated.
```

The same sample works on Linux (w/ libc++).  I suspect the problem lies somewhere in FreeBSD headers.

I can also reproduce with git clang (built from 1e67385d28a4462b3badb40373cd05d91f8ebce5) but against system libc++.
Comment 1 Dimitry Andric freebsd_committer freebsd_triage 2022-06-18 13:39:56 UTC
I'm unsure why -fmodules changes this behavior, but __stdoutp is declared in /usr/include/stdio.h, like:

#ifndef _STDSTREAM_DECLARED
__BEGIN_DECLS
extern FILE *__stdinp;
extern FILE *__stdoutp;
extern FILE *__stderrp;
__END_DECLS
#define _STDSTREAM_DECLARED
#endif

while there is a similar, but slightly different declaration in /usr/include/wchar.h:

#ifndef _STDSTREAM_DECLARED
extern FILE *__stdinp;
extern FILE *__stdoutp;
extern FILE *__stderrp;
#define _STDSTREAM_DECLARED
#endif

Though in this file, __BEGIN_DECLS is at the top, and __END_DECLS at the bottom.

libc++'s <cstdio.h> begins by including <__config>, and then <stdio.h>. I suspect the former is pulling in <wchar.h>, or something like that?

On the other hand, you might be the very first person to ever try modules on FreeBSD. There are likely some bumps in the road. :)
Comment 2 Dimitry Andric freebsd_committer freebsd_triage 2022-06-18 13:55:26 UTC
This appears to fix it:

--- /usr/include/c++/v1/module.modulemap.org    2022-01-07 17:11:14.993926000 +0100
+++ /usr/include/c++/v1/module.modulemap        2022-06-18 15:52:04.275940000 +0200
@@ -180,7 +180,7 @@
       export *
     }
     module cstdio {
-      header "cstdio"
+      textual header "cstdio"
       export *
     }
     module cstdlib {

E.g. I looked at the module for <stdio.h>, and that has a comment:

    module stdio_h {
      // <stdio.h>'s __need_* macros require textual inclusion.
      textual header "stdio.h"
      export *
      export Darwin.C.stdio
    }

We don't have "__need_*" macros, but equivalent _*_DEFINED and _*_DECLARED ones.

It looks like clang modules can't work yet without the "textual" keyword on a bunch of our headers...
Comment 3 Michał Górny 2022-06-18 15:09:41 UTC
Well, it wasn't my choice, really ;-).  Apparently use of gmodules was enabled in LLDB's test suite on FreeBSD when the relevant tests were added — i.e. in 2016.  Unless I'm missing something, we've been doing some testing with them since then.  I'm guessing there were some recent changes to clang or libc++ that made them more strict.

Ah, sorry, I don't think the 'wchar.h' part is actually the problem — I think it just blames the wrong header.  The underlying problem is that the stdout/stderr/stdin macros aren't visible from <cstdio>.

I know very little about modules myself but FWICS the purpose here is to avoid relying on indirect includes.  I think textual header breaks that, i.e. the following test program should fail:

```
#include <cstdio>

int main() {
  FILE *f;
}
```

With the original header variant, it fails as expected (just blames the wrong header):

```
$ clang++ -fmodules test.cxx 
test.cxx:4:3: error: missing '#include <wchar.h>'; 'FILE' must be declared before it is used
  FILE *f;
  ^
/usr/include/wchar.h:109:24: note: declaration here is not visible
typedef struct __sFILE FILE;
                       ^
1 error generated.
```

Changing `header` to `textual header` makes it build, so I think that goes against the intent.  I suppose there must be a better way of solving stdout/stderr/stdin problem.
Comment 4 Dimitry Andric freebsd_committer freebsd_triage 2022-06-18 15:48:13 UTC
(In reply to Michał Górny from comment #3)
Well libc++'s <cstdio> simply includes <stdio.h>, so whatever applies to the latter must also apply to the former, I think?

On e.g. macOS, stdio.h is simpler with respect to __stdoutp and friends:

#include <_stdio.h>

__BEGIN_DECLS
extern FILE *__stdinp;
extern FILE *__stdoutp;
extern FILE *__stderrp;
__END_DECLS

On Linux with glibc, there are no tricks with __, they just declare:

/* Standard streams.  */
extern FILE *stdin;             /* Standard input stream.  */
extern FILE *stdout;            /* Standard output stream.  */
extern FILE *stderr;            /* Standard error output stream.  */
/* C89/C99 say they're macros.  Make them happy.  */
#define stdin stdin
#define stdout stdout
#define stderr stderr

but there are various other defines that use _need_this and _need_that.

As far as I understand the modules system, this looks like one of the as-of-yet unsolved problems in modules in general? I.e. should all system headers be rewritten to "support" -fmodules? I don't think that is going to happen on many operating systems..
Comment 5 Mark Millard 2022-06-18 23:36:43 UTC
As I understand it, the C++20 standard makes a distinction between:

A) C++ headers ( such as <chrono> )
vs.
B) C headers adopted by C++ ( such as <cassert> or <cstdio> )

So that:

export module NAME0;
import <chrono>
. . .

is supposed to be known to be okay. But:

export module NAME1;
import <cstdio>
. . .

is not portable and could give an error as a result
without violating the standard.

It leaves me wondering if the specific example
presented has wondered outside what is actually
supposed to be guaranteed by the C++20 standard.
(At this point I do not know.) The context is
different in the example but it might be related.
Comment 6 Mark Millard 2022-06-20 05:17:21 UTC
(In reply to Mark Millard from comment #5)

The C++20ish terminology here seems to be:

"importable C++ library headers"

See, for example, https://eel.is/c++draft/headers that
has a draft table listing them. It also has a separate
table for "C++ headers for C library facilities" that
are not classified as (guaranteed) importable.

<cstdio> is not guaranteed as importable in this draft
material.

I'm still unclear on if the #include use in the example
is a context for an implicit conversion to an import
according to the draft standard effort (or would be for
an importable C++ library header, anyway?). So I'm still
unclear if the example wondered outside the (draft)
guaranteed language properties.
Comment 7 Mark Millard 2022-06-25 07:45:25 UTC
(In reply to Mark Millard from comment #6)

If a #include lists a importable header, it is implementation
defined for if the #include is replaced by an import directive
for the header in question or not.

But <cstdio> does not have to be importable: it is not listed
in Table 21 of, for example, INCITS/ISO/IEC 14882:2020 (2021).

So it appears that definitions outside 14882:2020 need to be
used to establish the expected (but not necessarily portable)
behavior for the example.