LTO优化全局变量



我看到LTO从TU中优化一些全局对象,如果该TU中没有显式地来自另一个TU的函数。

下面的摘录试图描述所涉及的关键类和文件(请注意,这只是为了演示目的,可能在所有地方都不完全准确):

我有一个单例类Registrar,它维护一个包含已构造的Foo类型的所有对象的列表。为了避免静态构造顺序的失败,我在构造第一个类型Foo的对象时动态地构造这个对象的实例。

// Registrar.hpp
class Registrar
{
public:
static Registrar * sRegistrar;
std::vector<Foo *> objectList;
Registrar() = default;
};

接下来,我们有类Foo。这个类的实例如上所述在Registrar中注册。

// Foo.hpp
class Foo
{
public:
Foo()
{
if (Registrar::sRegistrar == nullptr)
Registrar::sRegistrar = new Registrar();
Registrar::sRegistrar->objectList.push_back(this);
}
};

Foo的实例是全局的,可以从几个文件创建。在一个这样的文件中,我们碰巧定义了另一个函数,可以从其他地方调用:

// file1.hpp
void someFunctionThatIsCalledExplicitly()
{
doSomething();
}
namespace 
{
__attribute__((used, retain))
Foo f1;
}

但是在另一个文件中,我们只创建了一个Foo的实例:

// file2.hpp
namespace 
{
__attribute__((used, retain))
Foo f2;
}

我所看到的是f2正在得到优化,而f1则没有,尽管为Foo类的所有声明添加了__attribute__((used, retain))

我应该如何防止LTO优化出这些实例?为什么属性没有区别?

编辑:我可以写一个小例子来重现上述问题。

  1. main.cpp:
#include <iostream>
#include "Registrar.hpp"
#ifdef FORCE_LINKAGE
extern int i;
#endif
extern void someFunctionThatIsCalledExplicitly();
int main()
{
#ifdef FORCE_LINKAGE
i++;
#endif
someFunctionThatIsCalledExplicitly();
if (Registrar::sRegistrar == nullptr)
{
std::cout << "No instances of foo";
}
else
{
std::cout << Registrar::sRegistrar->objectList.size() << " instances of foon";
}
return 0;
}
  • Foo.hpp
  • #pragma once
    class Foo
    {
    public:
    Foo();
    };
    
  • Foo.cpp:
  • #include "Foo.hpp"
    #include "Registrar.hpp"
    Foo::Foo()
    {
    if (Registrar::sRegistrar == nullptr)
    {
    Registrar::sRegistrar = new Registrar();
    }
    Registrar::sRegistrar->objectList.push_back(this);
    }
    
    4
  • Registrar.hpp:
  • #pragma once
    #include <vector>
    #include "Foo.hpp"
    class Registrar
    {
    public:
    static Registrar * sRegistrar;
    std::vector<Foo *> objectList;
    Registrar() = default;
    };
    
  • Registrar.cpp:
  • #include "Registrar.hpp"
    Registrar * Registrar::sRegistrar = nullptr;
    
  • File1.cpp:
  • #include <iostream>
    #include "Foo.hpp"
    void someFunctionThatIsCalledExplicitly()
    {
    std::cout << "someFunctionThatIsCalledExplicitly() calledn";
    }
    namespace
    {
    __attribute__((used, retain))
    Foo f1;
    }
    
  • File2.cpp:
  • #include "Foo.hpp"
    #ifdef FORCE_LINKAGE
    int i = 0;
    #endif
    namespace
    {
    __attribute__((used, retain))
    Foo f2;
    }
    
  • Makefile:
  • CC          = clang++
    LIBTOOL     = libtool
    BUILDDIR    = build
    BINFILE     = lto
    BUILDFLAGS  = -flto -std=c++17
    LINKFLAGS   = -flto
    .PHONY:     all
    all:        $(BUILDDIR) $(BINFILE)
    .PHONY:     force
    force:      def all
    .PHONY:     def
    def:
    $(eval BUILDFLAGS += -DFORCE_LINKAGE)
    $(BINFILE): foo files
    $(CC) -o $(BUILDDIR)/$@ $(LINKFLAGS) -L$(BUILDDIR) $(addprefix -l, $^)
    foo:        Foo.o main.o Registrar.o
    $(LIBTOOL) $(STATIC) -o $(BUILDDIR)/lib$@.a $(addprefix $(BUILDDIR)/, $^)
    files:  File1.o File2.o
    $(LIBTOOL) $(STATIC) -o $(BUILDDIR)/lib$@.a $(addprefix $(BUILDDIR)/, $^)
    %.o:        %.cpp
    $(CC) $(BUILDFLAGS) -c -o $(addprefix $(BUILDDIR)/, $@) $<
    .PHONY:     $(BUILDDIR)
    $(BUILDDIR):
    mkdir -p $(BUILDDIR)
    .PHONY:     clean
    clean:
    rm -rf $(BUILDDIR)
    

    我有两个版本,一个类似于以上(我只看到一个实例),另一个在我力连杆通过声明一个全局变量,我指的是其他地方(这里我看到两个实例):

    $ make
    $ ./build/lto
    someFunctionThatIsCalledExplicitly() called
    1 instances of foo
    $ make force
    $ ./build/lto
    someFunctionThatIsCalledExplicitly() called
    2 instances of foo
    

    好吧,我做了一些挖掘,事实上你链接的。a库是这里的罪魁祸首,不是LTO,也不是任何其他优化。

    这在SO之前已经提到过,顺便说一下,参见:静态库的静态初始化和销毁's全局变量在g++中不会发生

    当链接。o文件时(就像我在godbolt上所做的那样),所有的东西都进去了,它工作了。

    对于。a文件,只有被引用的代码被链接,其余的不被链接。创建虚拟变量是一种解决方法,但正确的方法是将--whole-archive传递给链接器。

    由于libtool的问题,我无法运行基于makefile的示例,但请查看我的CMake配置:

    cmake_minimum_required(VERSION 3.18)
    project(LINK)
    
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
    add_library(Files File1.cpp File2.cpp)
    
    target_include_directories(Files
    INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
    )
    target_compile_definitions(Files PUBLIC ${FORCE})
    add_executable(test Foo.cpp main.cpp Registrar.cpp)
    # note the line below
    target_link_libraries(test -Wl,--whole-archive Files -Wl,--no-whole-archive)
    target_compile_definitions(test PUBLIC ${FORCE})
    

    链接时,它将以以下方式调用命令:

    g++ -o test -Wl, --whole-archive -l:libFiles.a -Wl, --no-whole-archive Foo.o Registrar.o main.o

    最新更新