如果一个项目没有包含,但仅使用C++20模块,编译器会看到每个函数体吗?

问题描述 投票:0回答:2

以这段代码为例(用于演讲 LLVM Optimization Remarks - Ofek Shilon - CppCon 2022,at 21:28):

void somefunc(const int&);
int whateva();

void f(int i, int* res)
{
    somefunc(i);
    i++;
    res[0] = whateva();
    i++;
    res[1] = whateva();
    i++;
    res[2] = whateva();
} 

由于

somefunc
whateva
的主体不可用于此翻译单元(从现在起为 TU)中的编译器,因此可能会错过很多优化机会。

如果您的项目仅使用 C++20 模块,这个问题是否“解决”了?据我所知,在使用模块时,编译器会生成一些包含有关模块的元数据的文件,类似于预编译头,我们将其称为预编译模块,并且这些预编译模块必须包含在任何其他模块中(在命令行上)导入它们。

这是否意味着在使用模块时“翻译单元边界悲观化”(我编造了这个名字;我不知道它是否有一个)肯定消失了,因为所有模块都会看到所有的主体(感谢附加的预编译-每个功能的模块)?

换句话说,如果您的项目不使用任何单个

#include
(动态链接库除外),那么新的“编译器可见性边界”(忽略 LTO)是什么?

c++ c++20 compiler-optimization c++-modules
2个回答
5
投票

如果您的项目仅使用 C++20 模块,这个问题“解决”了吗?

没有。

可以将这些函数定义(

inline
d)放入模块接口文件中。这将使编译器在导入此类模块时可以使用函数定义。但这并不仅仅因为您将代码构建为模块而发生。如果您将该函数放入模块实现单元中,那么导入该模块的 TU 将看不到该函数。

这与将定义放入标头和源文件中本质上没有什么不同。主要区别在于,对于模块,编译器不必在每次导入文件时编译该定义的代码。

传递导入(导入模块 A,模块 A 导入模块 B,但不导出它)将保留可见性。即使您无法直接访问模块 B,通过导入 A,编译器可见的所有 B 内容也将对您的 TU 可见。因此,如果您调用 A 的内联函数,而该函数又调用 B 的内联函数,则内联可以正确传播。


2
投票

不,模块不会取代该边界。您仍然需要 LTO 来跨 TU 进行优化。

如果你想自己验证这一点,你可以查看CMI文件的内容。例如,我创建了一个模块,该模块导出一个调用

puts ("foo")
的函数,但该字符串从未出现在生成的模块中:

~/f$ gcc -c -O3 -std=c++23 -fmodules-ts modtest.cpp
~/f$ grep foo gcm.cache/helloworld.gcm || echo no match
no match

LTO 如今通常非常有用(例如,许多 GNU/Linux 发行版几乎都用它构建了所有软件包,就像我一样),因此,需要更改现有代码的新解决方案并不是真正必要的,并且模块旨在解决另一个问题(TU 之间缺乏逻辑模块化)

© www.soinside.com 2019 - 2024. All rights reserved.