mirror of
https://github.com/RPCSX/llvm.git
synced 2024-11-27 21:50:40 +00:00
libLTO: add a ThinLTOCodeGenerator on the model of LTOCodeGenerator.
This is intended to provide a parallel (threaded) ThinLTO scheme for linker plugin use through the libLTO C API. The intent of this patch is to provide a first implementation as a proof-of-concept and allows linker to start supporting ThinLTO by definiing the libLTO C API. Some part of the libLTO API are left unimplemented yet. Following patches will add support for these. The current implementation can link all clang/llvm binaries. Differential Revision: http://reviews.llvm.org/D17066 From: Mehdi Amini <mehdi.amini@apple.com> git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@262977 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
parent
ec57137c98
commit
73cf01b193
@ -40,8 +40,7 @@ typedef bool lto_bool_t;
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define LTO_API_VERSION 17
|
||||
|
||||
#define LTO_API_VERSION 18
|
||||
/**
|
||||
* \since prior to LTO_API_VERSION=3
|
||||
*/
|
||||
@ -91,6 +90,9 @@ typedef struct LLVMOpaqueLTOModule *lto_module_t;
|
||||
/** opaque reference to a code generator */
|
||||
typedef struct LLVMOpaqueLTOCodeGenerator *lto_code_gen_t;
|
||||
|
||||
/** opaque reference to a thin code generator */
|
||||
typedef struct LLVMOpaqueThinLTOCodeGenerator *thinlto_code_gen_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -548,6 +550,221 @@ extern void
|
||||
lto_codegen_set_should_embed_uselists(lto_code_gen_t cg,
|
||||
lto_bool_t ShouldEmbedUselists);
|
||||
|
||||
/**
|
||||
* @}
|
||||
* @defgroup LLVMCTLTO ThinLTO
|
||||
* @ingroup LLVMC
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Type to wrap a single object returned by ThinLTO.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
typedef struct {
|
||||
void *Buffer;
|
||||
size_t Size;
|
||||
} LTOObjectBuffer;
|
||||
|
||||
/**
|
||||
* Instantiates a ThinLTO code generator.
|
||||
* Returns NULL on error (check lto_get_error_message() for details).
|
||||
*
|
||||
*
|
||||
* The ThinLTOCodeGenerator is not intended to be reuse for multiple
|
||||
* compilation: the model is that the client adds modules to the generator and
|
||||
* ask to perform the ThinLTO optimizations / codegen, and finally destroys the
|
||||
* codegenerator.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern thinlto_code_gen_t thinlto_create_codegen();
|
||||
|
||||
/**
|
||||
* Frees the generator and all memory it internally allocated.
|
||||
* Upon return the thinlto_code_gen_t is no longer valid.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_dispose(thinlto_code_gen_t cg);
|
||||
|
||||
/**
|
||||
* Add a module to a ThinLTO code generator. Identifier has to be unique among
|
||||
* all the modules in a code generator. The data buffer stays owned by the
|
||||
* client, and is expected to be available for the entire lifetime of the
|
||||
* thinlto_code_gen_t it is added to.
|
||||
*
|
||||
* On failure, returns NULL (check lto_get_error_message() for details).
|
||||
*
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_add_module(thinlto_code_gen_t cg,
|
||||
const char *identifier, const char *data,
|
||||
int length);
|
||||
|
||||
/**
|
||||
* Optimize and codegen all the modules added to the codegenerator using
|
||||
* ThinLTO. Resulting objects are accessible using thinlto_module_get_object().
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_process(thinlto_code_gen_t cg);
|
||||
|
||||
/**
|
||||
* Returns the number of object files produced by the ThinLTO CodeGenerator.
|
||||
*
|
||||
* It usually matches the number of input files, but this is not a guarantee of
|
||||
* the API and may change in future implementation, so the client should not
|
||||
* assume it.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern unsigned int thinlto_module_get_num_objects(thinlto_code_gen_t cg);
|
||||
|
||||
/**
|
||||
* Returns a reference to the ith object file produced by the ThinLTO
|
||||
* CodeGenerator.
|
||||
*
|
||||
* Client should use \p thinlto_module_get_num_objects() to get the number of
|
||||
* available objects.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern LTOObjectBuffer thinlto_module_get_object(thinlto_code_gen_t cg,
|
||||
unsigned int index);
|
||||
|
||||
/**
|
||||
* Sets which PIC code model to generate.
|
||||
* Returns true on error (check lto_get_error_message() for details).
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern lto_bool_t thinlto_codegen_set_pic_model(thinlto_code_gen_t cg,
|
||||
lto_codegen_model);
|
||||
|
||||
/**
|
||||
* @}
|
||||
* @defgroup LLVMCTLTO_CACHING ThinLTO Cache Control
|
||||
* @ingroup LLVMCTLTO
|
||||
*
|
||||
* These entry points control the ThinLTO cache. The cache is intended to
|
||||
* support incremental build, and thus needs to be persistent accross build.
|
||||
* The client enabled the cache by supplying a path to an existing directory.
|
||||
* The code generator will use this to store objects files that may be reused
|
||||
* during a subsequent build.
|
||||
* To avoid filling the disk space, a few knobs are provided:
|
||||
* - The pruning interval limit the frequency at which the garbage collector
|
||||
* will try to scan the cache directory to prune it from expired entries.
|
||||
* Setting to -1 disable the pruning (default).
|
||||
* - The pruning expiration time indicates to the garbage collector how old an
|
||||
* entry needs to be to be removed.
|
||||
* - Finally, the garbage collector can be instructed to prune the cache till
|
||||
* the occupied space goes below a threshold.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the path to a directory to use as a cache storage for incremental build.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_set_cache_dir(thinlto_code_gen_t cg,
|
||||
const char *cache_dir);
|
||||
|
||||
/**
|
||||
* Sets the cache pruning interval (in seconds). A negative value disable the
|
||||
* pruning (default).
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_set_cache_pruning_interval(thinlto_code_gen_t cg,
|
||||
int interval);
|
||||
|
||||
/**
|
||||
* Sets the maximum cache size that can be persistent across build, in terms of
|
||||
* percentage of the available space on the the disk. Set to 100 to indicate
|
||||
* no limit, 50 to indicate that the cache size will not be left over half the
|
||||
* available space. A value over 100 will be reduced to 100.
|
||||
*
|
||||
* The formula looks like:
|
||||
* AvailableSpace = FreeSpace + ExistingCacheSize
|
||||
* NewCacheSize = AvailableSpace * P/100
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_set_final_cache_size_relative_to_available_space(
|
||||
thinlto_code_gen_t cg, unsigned percentage);
|
||||
|
||||
/**
|
||||
* Sets the expiration (in seconds) for an entry in the cache.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_set_cache_entry_expiration(thinlto_code_gen_t cg,
|
||||
unsigned expiration);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the path to a directory to use as a storage for temporary bitcode files.
|
||||
* The intention is to make the bitcode files available for debugging at various
|
||||
* stage of the pipeline.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_set_savetemps_dir(thinlto_code_gen_t cg,
|
||||
const char *save_temps_dir);
|
||||
|
||||
/**
|
||||
* Sets the cpu to generate code for.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_set_cpu(thinlto_code_gen_t cg, const char *cpu);
|
||||
|
||||
/**
|
||||
* Parse -mllvm style debug options.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_debug_options(const char *const *options, int number);
|
||||
|
||||
/**
|
||||
* Test if a module has support for ThinLTO linking.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern bool lto_module_is_thinlto(lto_module_t mod);
|
||||
|
||||
/**
|
||||
* Adds a symbol to the list of global symbols that must exist in the final
|
||||
* generated code. If a function is not listed there, it might be inlined into
|
||||
* every usage and optimized away. For every single module, the functions
|
||||
* referenced from code outside of the ThinLTO modules need to be added here.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_add_must_preserve_symbol(thinlto_code_gen_t cg,
|
||||
const char *name,
|
||||
int length);
|
||||
|
||||
/**
|
||||
* Adds a symbol to the list of global symbols that are cross-referenced between
|
||||
* ThinLTO files. If the ThinLTO CodeGenerator can ensure that every
|
||||
* references from a ThinLTO module to this symbol is optimized away, then
|
||||
* the symbol can be discarded.
|
||||
*
|
||||
* \since LTO_API_VERSION=18
|
||||
*/
|
||||
extern void thinlto_codegen_add_cross_referenced_symbol(thinlto_code_gen_t cg,
|
||||
const char *name,
|
||||
int length);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -386,6 +386,13 @@ auto find(R &&Range, const T &val) -> decltype(Range.begin()) {
|
||||
return std::find(Range.begin(), Range.end(), val);
|
||||
}
|
||||
|
||||
/// Provide wrappers to std::find_if which take ranges instead of having to pass
|
||||
/// begin/end explicitly.
|
||||
template <typename R, class T>
|
||||
auto find_if(R &&Range, const T &Pred) -> decltype(Range.begin()) {
|
||||
return std::find_if(Range.begin(), Range.end(), Pred);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Extra additions to <memory>
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -65,6 +65,9 @@ public:
|
||||
static bool isBitcodeFile(const void *mem, size_t length);
|
||||
static bool isBitcodeFile(const char *path);
|
||||
|
||||
/// Returns 'true' if the Module is produced for ThinLTO.
|
||||
bool isThinLTO();
|
||||
|
||||
/// Returns 'true' if the memory buffer is LLVM bitcode for the specified
|
||||
/// triple.
|
||||
static bool isBitcodeForTarget(MemoryBuffer *memBuffer,
|
||||
|
233
include/llvm/LTO/ThinLTOCodeGenerator.h
Normal file
233
include/llvm/LTO/ThinLTOCodeGenerator.h
Normal file
@ -0,0 +1,233 @@
|
||||
//===-ThinLTOCodeGenerator.h - LLVM Link Time Optimizer -------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file declares the ThinLTOCodeGenerator class, similar to the
|
||||
// LTOCodeGenerator but for the ThinLTO scheme. It provides an interface for
|
||||
// linker plugin.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LTO_THINLTOCODEGENERATOR_H
|
||||
#define LLVM_LTO_THINLTOCODEGENERATOR_H
|
||||
|
||||
#include "llvm-c/lto.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/StringSet.h"
|
||||
#include "llvm/ADT/Triple.h"
|
||||
#include "llvm/Support/CodeGen.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Target/TargetOptions.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace llvm {
|
||||
class FunctionInfoIndex;
|
||||
class LLVMContext;
|
||||
class TargetMachine;
|
||||
|
||||
/// Helper to gather options relevant to the target machine creation
|
||||
struct TargetMachineBuilder {
|
||||
Triple TheTriple;
|
||||
std::string MCpu;
|
||||
std::string MAttr;
|
||||
TargetOptions Options;
|
||||
Reloc::Model RelocModel = Reloc::Default;
|
||||
CodeGenOpt::Level CGOptLevel = CodeGenOpt::Default;
|
||||
|
||||
std::unique_ptr<TargetMachine> create() const;
|
||||
};
|
||||
|
||||
/// This class define an interface similar to the LTOCodeGenerator, but adapted
|
||||
/// for ThinLTO processing.
|
||||
/// The ThinLTOCodeGenerator is not intended to be reuse for multiple
|
||||
/// compilation: the model is that the client adds modules to the generator and
|
||||
/// ask to perform the ThinLTO optimizations / codegen, and finally destroys the
|
||||
/// codegenerator.
|
||||
class ThinLTOCodeGenerator {
|
||||
public:
|
||||
/// Add given module to the code generator.
|
||||
void addModule(StringRef Identifier, StringRef Data);
|
||||
|
||||
/**
|
||||
* Adds to a list of all global symbols that must exist in the final generated
|
||||
* code. If a symbol is not listed there, it will be optimized away if it is
|
||||
* inlined into every usage.
|
||||
*/
|
||||
void preserveSymbol(StringRef Name);
|
||||
|
||||
/**
|
||||
* Adds to a list of all global symbols that are cross-referenced between
|
||||
* ThinLTO files. If the ThinLTO CodeGenerator can ensure that every
|
||||
* references from a ThinLTO module to this symbol is optimized away, then
|
||||
* the symbol can be discarded.
|
||||
*/
|
||||
void crossReferenceSymbol(StringRef Name);
|
||||
|
||||
/**
|
||||
* Process all the modules that were added to the code generator in parallel.
|
||||
*
|
||||
* Client can access the resulting object files using getProducedBinaries()
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* Return the "in memory" binaries produced by the code generator.
|
||||
*/
|
||||
std::vector<std::unique_ptr<MemoryBuffer>> &getProducedBinaries() {
|
||||
return ProducedBinaries;
|
||||
}
|
||||
|
||||
/**
|
||||
* \defgroup Options setters
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* \defgroup Cache controlling options
|
||||
*
|
||||
* These entry points control the ThinLTO cache. The cache is intended to
|
||||
* support incremental build, and thus needs to be persistent accross build.
|
||||
* The client enabled the cache by supplying a path to an existing directory.
|
||||
* The code generator will use this to store objects files that may be reused
|
||||
* during a subsequent build.
|
||||
* To avoid filling the disk space, a few knobs are provided:
|
||||
* - The pruning interval limit the frequency at which the garbage collector
|
||||
* will try to scan the cache directory to prune it from expired entries.
|
||||
* Setting to -1 disable the pruning (default).
|
||||
* - The pruning expiration time indicates to the garbage collector how old
|
||||
* an entry needs to be to be removed.
|
||||
* - Finally, the garbage collector can be instructed to prune the cache till
|
||||
* the occupied space goes below a threshold.
|
||||
* @{
|
||||
*/
|
||||
|
||||
struct CachingOptions {
|
||||
std::string Path;
|
||||
int PruningInterval = -1; // seconds, -1 to disable pruning
|
||||
unsigned int Expiration; // seconds.
|
||||
unsigned MaxPercentageOfAvailableSpace; // percentage.
|
||||
};
|
||||
|
||||
/// Provide a path to a directory where to store the cached files for
|
||||
/// incremental build.
|
||||
void setCacheDir(std::string Path) { CacheOptions.Path = std::move(Path); }
|
||||
|
||||
/// Cache policy: interval (seconds) between two prune of the cache. Set to a
|
||||
/// negative value (default) to disable pruning.
|
||||
void setCachePruningInterval(int Interval) {
|
||||
CacheOptions.PruningInterval = Interval;
|
||||
}
|
||||
|
||||
/// Cache policy: expiration (in seconds) for an entry.
|
||||
void setCacheEntryExpiration(unsigned Expiration) {
|
||||
CacheOptions.Expiration = Expiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum cache size that can be persistent across build, in terms
|
||||
* of percentage of the available space on the the disk. Set to 100 to
|
||||
* indicate no limit, 50 to indicate that the cache size will not be left over
|
||||
* half the available space. A value over 100 will be reduced to 100.
|
||||
*
|
||||
* The formula looks like:
|
||||
* AvailableSpace = FreeSpace + ExistingCacheSize
|
||||
* NewCacheSize = AvailableSpace * P/100
|
||||
*
|
||||
*/
|
||||
void setMaxCacheSizeRelativeToAvailableSpace(unsigned Percentage) {
|
||||
CacheOptions.MaxPercentageOfAvailableSpace = Percentage;
|
||||
}
|
||||
|
||||
/**@}*/
|
||||
|
||||
/// Set the path to a directory where to save temporaries at various stages of
|
||||
/// the processing.
|
||||
void setSaveTempsDir(std::string Path) { SaveTempsDir = std::move(Path); }
|
||||
|
||||
/// CPU to use to initialize the TargetMachine
|
||||
void setCpu(std::string Cpu) { TMBuilder.MCpu = std::move(Cpu); }
|
||||
|
||||
/// Subtarget attributes
|
||||
void setAttr(std::string MAttr) { TMBuilder.MAttr = std::move(MAttr); }
|
||||
|
||||
/// TargetMachine options
|
||||
void setTargetOptions(TargetOptions Options) {
|
||||
TMBuilder.Options = std::move(Options);
|
||||
}
|
||||
|
||||
/// CodeModel
|
||||
void setCodePICModel(Reloc::Model Model) { TMBuilder.RelocModel = Model; }
|
||||
|
||||
/// CodeGen optimization level
|
||||
void setCodeGenOptLevel(CodeGenOpt::Level CGOptLevel) {
|
||||
TMBuilder.CGOptLevel = CGOptLevel;
|
||||
}
|
||||
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* \defgroup Set of APIs to run individual stages in isolation.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Produce the combined function index from all the bitcode files:
|
||||
* "thin-link".
|
||||
*/
|
||||
std::unique_ptr<FunctionInfoIndex> linkCombinedIndex();
|
||||
|
||||
/**
|
||||
* Perform promotion and renaming of exported internal functions.
|
||||
*/
|
||||
void promote(Module &Module, FunctionInfoIndex &Index);
|
||||
|
||||
/**
|
||||
* Perform cross-module importing for the module identified by
|
||||
* ModuleIdentifier.
|
||||
*/
|
||||
void crossModuleImport(Module &Module, FunctionInfoIndex &Index);
|
||||
|
||||
/**
|
||||
* Perform post-importing ThinLTO optimizations.
|
||||
*/
|
||||
void optimize(Module &Module);
|
||||
|
||||
/**
|
||||
* Perform ThinLTO CodeGen.
|
||||
*/
|
||||
std::unique_ptr<MemoryBuffer> codegen(Module &Module);
|
||||
|
||||
/**@}*/
|
||||
|
||||
private:
|
||||
/// Helper factory to build a TargetMachine
|
||||
TargetMachineBuilder TMBuilder;
|
||||
|
||||
/// Vector holding the in-memory buffer containing the produced binaries.
|
||||
std::vector<std::unique_ptr<MemoryBuffer>> ProducedBinaries;
|
||||
|
||||
/// Vector holding the input buffers containing the bitcode modules to
|
||||
/// process.
|
||||
std::vector<MemoryBufferRef> Modules;
|
||||
|
||||
/// Set of symbols that need to be preserved outside of the set of bitcode
|
||||
/// files.
|
||||
StringSet<> PreservedSymbols;
|
||||
|
||||
/// Set of symbols that are cross-referenced between bitcode files.
|
||||
StringSet<> CrossReferencedSymbols;
|
||||
|
||||
/// Control the caching behavior.
|
||||
CachingOptions CacheOptions;
|
||||
|
||||
/// Path to a directory to save the temporary bitcode files.
|
||||
std::string SaveTempsDir;
|
||||
};
|
||||
}
|
||||
#endif
|
@ -1,9 +1,10 @@
|
||||
add_llvm_library(LLVMLTO
|
||||
LTOModule.cpp
|
||||
LTOCodeGenerator.cpp
|
||||
ThinLTOCodeGenerator.cpp
|
||||
|
||||
ADDITIONAL_HEADER_DIRS
|
||||
${LLVM_MAIN_INCLUDE_DIR}/llvm/LTO
|
||||
)
|
||||
)
|
||||
|
||||
add_dependencies(LLVMLTO intrinsics_gen)
|
||||
|
@ -34,3 +34,4 @@ required_libraries =
|
||||
Scalar
|
||||
Support
|
||||
Target
|
||||
TransformUtils
|
@ -75,6 +75,18 @@ bool LTOModule::isBitcodeFile(const char *Path) {
|
||||
return bool(BCData);
|
||||
}
|
||||
|
||||
bool LTOModule::isThinLTO() {
|
||||
// Right now the detection is only based on the summary presence. We may want
|
||||
// to add a dedicated flag at some point.
|
||||
return hasFunctionSummary(IRFile->getMemoryBufferRef(),
|
||||
[](const DiagnosticInfo &DI) {
|
||||
DiagnosticPrinterRawOStream DP(errs());
|
||||
DI.print(DP);
|
||||
errs() << '\n';
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
bool LTOModule::isBitcodeForTarget(MemoryBuffer *Buffer,
|
||||
StringRef TriplePrefix) {
|
||||
ErrorOr<MemoryBufferRef> BCOrErr =
|
||||
|
384
lib/LTO/ThinLTOCodeGenerator.cpp
Normal file
384
lib/LTO/ThinLTOCodeGenerator.cpp
Normal file
@ -0,0 +1,384 @@
|
||||
//===-ThinLTOCodeGenerator.cpp - LLVM Link Time Optimizer -----------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file implements the Thin Link Time Optimization library. This library is
|
||||
// intended to be used by linker to optimize code at link time.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "llvm/LTO/ThinLTOCodeGenerator.h"
|
||||
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/ADT/Statistic.h"
|
||||
#include "llvm/Analysis/TargetLibraryInfo.h"
|
||||
#include "llvm/Analysis/TargetTransformInfo.h"
|
||||
#include "llvm/Bitcode/ReaderWriter.h"
|
||||
#include "llvm/Bitcode/BitcodeWriterPass.h"
|
||||
#include "llvm/ExecutionEngine/ObjectMemoryBuffer.h"
|
||||
#include "llvm/IR/LLVMContext.h"
|
||||
#include "llvm/IR/DiagnosticPrinter.h"
|
||||
#include "llvm/IR/LegacyPassManager.h"
|
||||
#include "llvm/IR/Mangler.h"
|
||||
#include "llvm/IRReader/IRReader.h"
|
||||
#include "llvm/Linker/Linker.h"
|
||||
#include "llvm/MC/SubtargetFeature.h"
|
||||
#include "llvm/Object/FunctionIndexObjectFile.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
#include "llvm/Support/TargetRegistry.h"
|
||||
#include "llvm/Support/ThreadPool.h"
|
||||
#include "llvm/Target/TargetMachine.h"
|
||||
#include "llvm/Transforms/IPO.h"
|
||||
#include "llvm/Transforms/IPO/FunctionImport.h"
|
||||
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
|
||||
#include "llvm/Transforms/ObjCARC.h"
|
||||
#include "llvm/Transforms/Utils/FunctionImportUtils.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace {
|
||||
|
||||
static cl::opt<int> ThreadCount("threads",
|
||||
cl::init(std::thread::hardware_concurrency()));
|
||||
|
||||
static void diagnosticHandler(const DiagnosticInfo &DI) {
|
||||
DiagnosticPrinterRawOStream DP(errs());
|
||||
DI.print(DP);
|
||||
errs() << '\n';
|
||||
}
|
||||
|
||||
// Simple helper to load a module from bitcode
|
||||
static std::unique_ptr<Module>
|
||||
loadModuleFromBuffer(const MemoryBufferRef &Buffer, LLVMContext &Context,
|
||||
bool Lazy) {
|
||||
SMDiagnostic Err;
|
||||
ErrorOr<std::unique_ptr<Module>> ModuleOrErr(nullptr);
|
||||
if (Lazy) {
|
||||
ModuleOrErr =
|
||||
getLazyBitcodeModule(MemoryBuffer::getMemBuffer(Buffer, false), Context,
|
||||
/* ShouldLazyLoadMetadata */ Lazy);
|
||||
} else {
|
||||
ModuleOrErr = parseBitcodeFile(Buffer, Context);
|
||||
}
|
||||
if (std::error_code EC = ModuleOrErr.getError()) {
|
||||
Err = SMDiagnostic(Buffer.getBufferIdentifier(), SourceMgr::DK_Error,
|
||||
EC.message());
|
||||
Err.print("ThinLTO", errs());
|
||||
report_fatal_error("Can't load module, abort.");
|
||||
}
|
||||
return std::move(ModuleOrErr.get());
|
||||
}
|
||||
|
||||
// Simple helper to save temporary files for debug.
|
||||
static void saveTempBitcode(const Module &TheModule, StringRef TempDir,
|
||||
unsigned count, StringRef Suffix) {
|
||||
if (TempDir.empty())
|
||||
return;
|
||||
// User asked to save temps, let dump the bitcode file after import.
|
||||
auto SaveTempPath = TempDir + llvm::utostr(count) + Suffix;
|
||||
std::error_code EC;
|
||||
raw_fd_ostream OS(SaveTempPath.str(), EC, sys::fs::F_None);
|
||||
if (EC)
|
||||
report_fatal_error(Twine("Failed to open ") + SaveTempPath +
|
||||
" to save optimized bitcode\n");
|
||||
WriteBitcodeToFile(&TheModule, OS, true, false);
|
||||
}
|
||||
|
||||
static StringMap<MemoryBufferRef>
|
||||
generateModuleMap(const std::vector<MemoryBufferRef> &Modules) {
|
||||
StringMap<MemoryBufferRef> ModuleMap;
|
||||
for (auto &ModuleBuffer : Modules) {
|
||||
assert(ModuleMap.find(ModuleBuffer.getBufferIdentifier()) ==
|
||||
ModuleMap.end() &&
|
||||
"Expect unique Buffer Identifier");
|
||||
ModuleMap[ModuleBuffer.getBufferIdentifier()] = ModuleBuffer;
|
||||
}
|
||||
return ModuleMap;
|
||||
}
|
||||
|
||||
/// Provide a "loader" for the FunctionImporter to access function from other
|
||||
/// modules.
|
||||
class ModuleLoader {
|
||||
/// The context that will be used for importing.
|
||||
LLVMContext &Context;
|
||||
|
||||
/// Map from Module identifier to MemoryBuffer. Used by clients like the
|
||||
/// FunctionImported to request loading a Module.
|
||||
StringMap<MemoryBufferRef> &ModuleMap;
|
||||
|
||||
public:
|
||||
ModuleLoader(LLVMContext &Context, StringMap<MemoryBufferRef> &ModuleMap)
|
||||
: Context(Context), ModuleMap(ModuleMap) {}
|
||||
|
||||
/// Load a module on demand.
|
||||
std::unique_ptr<Module> operator()(StringRef Identifier) {
|
||||
return loadModuleFromBuffer(ModuleMap[Identifier], Context, /*Lazy*/ true);
|
||||
}
|
||||
};
|
||||
|
||||
static void promoteModule(Module &TheModule, const FunctionInfoIndex &Index) {
|
||||
if (renameModuleForThinLTO(TheModule, Index))
|
||||
report_fatal_error("renameModuleForThinLTO failed");
|
||||
}
|
||||
|
||||
static void crossImportIntoModule(Module &TheModule,
|
||||
const FunctionInfoIndex &Index,
|
||||
StringMap<MemoryBufferRef> &ModuleMap) {
|
||||
ModuleLoader Loader(TheModule.getContext(), ModuleMap);
|
||||
FunctionImporter Importer(Index, Loader);
|
||||
Importer.importFunctions(TheModule);
|
||||
}
|
||||
|
||||
static void optimizeModule(Module &TheModule, TargetMachine &TM) {
|
||||
// Populate the PassManager
|
||||
PassManagerBuilder PMB;
|
||||
PMB.LibraryInfo = new TargetLibraryInfoImpl(TM.getTargetTriple());
|
||||
PMB.Inliner = createFunctionInliningPass();
|
||||
// FIXME: should get it from the bitcode?
|
||||
PMB.OptLevel = 3;
|
||||
PMB.LoopVectorize = true;
|
||||
PMB.SLPVectorize = true;
|
||||
PMB.VerifyInput = true;
|
||||
PMB.VerifyOutput = false;
|
||||
|
||||
legacy::PassManager PM;
|
||||
|
||||
// Add the TTI (required to inform the vectorizer about register size for
|
||||
// instance)
|
||||
PM.add(createTargetTransformInfoWrapperPass(TM.getTargetIRAnalysis()));
|
||||
|
||||
// Add optimizations
|
||||
PMB.populateThinLTOPassManager(PM);
|
||||
PM.add(createObjCARCContractPass());
|
||||
|
||||
PM.run(TheModule);
|
||||
}
|
||||
|
||||
std::unique_ptr<MemoryBuffer> codegenModule(Module &TheModule,
|
||||
TargetMachine &TM) {
|
||||
SmallVector<char, 128> OutputBuffer;
|
||||
|
||||
// CodeGen
|
||||
{
|
||||
raw_svector_ostream OS(OutputBuffer);
|
||||
legacy::PassManager PM;
|
||||
if (TM.addPassesToEmitFile(PM, OS, TargetMachine::CGFT_ObjectFile,
|
||||
/* DisableVerify */ true))
|
||||
report_fatal_error("Failed to setup codegen");
|
||||
|
||||
// Run codegen now. resulting binary is in OutputBuffer.
|
||||
PM.run(TheModule);
|
||||
}
|
||||
return make_unique<ObjectMemoryBuffer>(std::move(OutputBuffer));
|
||||
}
|
||||
|
||||
static std::unique_ptr<MemoryBuffer>
|
||||
ProcessThinLTOModule(Module &TheModule, const FunctionInfoIndex &Index,
|
||||
StringMap<MemoryBufferRef> &ModuleMap, TargetMachine &TM,
|
||||
ThinLTOCodeGenerator::CachingOptions CacheOptions,
|
||||
StringRef SaveTempsDir, unsigned count) {
|
||||
|
||||
// Save temps: after IPO.
|
||||
saveTempBitcode(TheModule, SaveTempsDir, count, ".1.IPO.bc");
|
||||
|
||||
// "Benchmark"-like optimization: single-source case
|
||||
bool SingleModule = (ModuleMap.size() == 1);
|
||||
|
||||
if (!SingleModule) {
|
||||
promoteModule(TheModule, Index);
|
||||
|
||||
// Save temps: after promotion.
|
||||
saveTempBitcode(TheModule, SaveTempsDir, count, ".2.promoted.bc");
|
||||
|
||||
crossImportIntoModule(TheModule, Index, ModuleMap);
|
||||
|
||||
// Save temps: after cross-module import.
|
||||
saveTempBitcode(TheModule, SaveTempsDir, count, ".3.imported.bc");
|
||||
}
|
||||
|
||||
optimizeModule(TheModule, TM);
|
||||
|
||||
saveTempBitcode(TheModule, SaveTempsDir, count, ".3.opt.bc");
|
||||
|
||||
return codegenModule(TheModule, TM);
|
||||
}
|
||||
|
||||
// Initialize the TargetMachine builder for a given Triple
|
||||
static void initTMBuilder(TargetMachineBuilder &TMBuilder,
|
||||
const Triple &TheTriple) {
|
||||
// Set a default CPU for Darwin triples (copied from LTOCodeGenerator).
|
||||
// FIXME this looks pretty terrible...
|
||||
if (TMBuilder.MCpu.empty() && TheTriple.isOSDarwin()) {
|
||||
if (TheTriple.getArch() == llvm::Triple::x86_64)
|
||||
TMBuilder.MCpu = "core2";
|
||||
else if (TheTriple.getArch() == llvm::Triple::x86)
|
||||
TMBuilder.MCpu = "yonah";
|
||||
else if (TheTriple.getArch() == llvm::Triple::aarch64)
|
||||
TMBuilder.MCpu = "cyclone";
|
||||
}
|
||||
TMBuilder.TheTriple = std::move(TheTriple);
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
void ThinLTOCodeGenerator::addModule(StringRef Identifier, StringRef Data) {
|
||||
MemoryBufferRef Buffer(Data, Identifier);
|
||||
if (Modules.empty()) {
|
||||
// First module added, so initialize the triple and some options
|
||||
LLVMContext Context;
|
||||
Triple TheTriple(getBitcodeTargetTriple(Buffer, Context));
|
||||
initTMBuilder(TMBuilder, Triple(TheTriple));
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
else {
|
||||
LLVMContext Context;
|
||||
assert(TMBuilder.TheTriple.str() ==
|
||||
getBitcodeTargetTriple(Buffer, Context) &&
|
||||
"ThinLTO modules with different triple not supported");
|
||||
}
|
||||
#endif
|
||||
Modules.push_back(Buffer);
|
||||
}
|
||||
|
||||
void ThinLTOCodeGenerator::preserveSymbol(StringRef Name) {
|
||||
PreservedSymbols.insert(Name);
|
||||
}
|
||||
|
||||
void ThinLTOCodeGenerator::crossReferenceSymbol(StringRef Name) {
|
||||
CrossReferencedSymbols.insert(Name);
|
||||
}
|
||||
|
||||
// TargetMachine factory
|
||||
std::unique_ptr<TargetMachine> TargetMachineBuilder::create() const {
|
||||
std::string ErrMsg;
|
||||
const Target *TheTarget =
|
||||
TargetRegistry::lookupTarget(TheTriple.str(), ErrMsg);
|
||||
if (!TheTarget) {
|
||||
report_fatal_error("Can't load target for this Triple: " + ErrMsg);
|
||||
}
|
||||
|
||||
// Use MAttr as the default set of features.
|
||||
SubtargetFeatures Features(MAttr);
|
||||
Features.getDefaultSubtargetFeatures(TheTriple);
|
||||
std::string FeatureStr = Features.getString();
|
||||
return std::unique_ptr<TargetMachine>(TheTarget->createTargetMachine(
|
||||
TheTriple.str(), MCpu, FeatureStr, Options, RelocModel,
|
||||
CodeModel::Default, CGOptLevel));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the combined function index from all the bitcode files:
|
||||
* "thin-link".
|
||||
*/
|
||||
std::unique_ptr<FunctionInfoIndex> ThinLTOCodeGenerator::linkCombinedIndex() {
|
||||
std::unique_ptr<FunctionInfoIndex> CombinedIndex;
|
||||
uint64_t NextModuleId = 0;
|
||||
for (auto &ModuleBuffer : Modules) {
|
||||
ErrorOr<std::unique_ptr<object::FunctionIndexObjectFile>> ObjOrErr =
|
||||
object::FunctionIndexObjectFile::create(ModuleBuffer, diagnosticHandler,
|
||||
false);
|
||||
if (std::error_code EC = ObjOrErr.getError()) {
|
||||
// FIXME diagnose
|
||||
errs() << "error: can't create FunctionIndexObjectFile for buffer: "
|
||||
<< EC.message() << "\n";
|
||||
return nullptr;
|
||||
}
|
||||
auto Index = (*ObjOrErr)->takeIndex();
|
||||
if (CombinedIndex) {
|
||||
CombinedIndex->mergeFrom(std::move(Index), ++NextModuleId);
|
||||
} else {
|
||||
CombinedIndex = std::move(Index);
|
||||
}
|
||||
}
|
||||
return CombinedIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform promotion and renaming of exported internal functions.
|
||||
*/
|
||||
void ThinLTOCodeGenerator::promote(Module &TheModule,
|
||||
FunctionInfoIndex &Index) {
|
||||
promoteModule(TheModule, Index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform cross-module importing for the module identified by ModuleIdentifier.
|
||||
*/
|
||||
void ThinLTOCodeGenerator::crossModuleImport(Module &TheModule,
|
||||
FunctionInfoIndex &Index) {
|
||||
auto ModuleMap = generateModuleMap(Modules);
|
||||
crossImportIntoModule(TheModule, Index, ModuleMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post-importing ThinLTO optimizations.
|
||||
*/
|
||||
void ThinLTOCodeGenerator::optimize(Module &TheModule) {
|
||||
initTMBuilder(TMBuilder, Triple(TheModule.getTargetTriple()));
|
||||
optimizeModule(TheModule, *TMBuilder.create());
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform ThinLTO CodeGen.
|
||||
*/
|
||||
std::unique_ptr<MemoryBuffer> ThinLTOCodeGenerator::codegen(Module &TheModule) {
|
||||
initTMBuilder(TMBuilder, Triple(TheModule.getTargetTriple()));
|
||||
return codegenModule(TheModule, *TMBuilder.create());
|
||||
}
|
||||
|
||||
// Main entry point for the ThinLTO processing
|
||||
void ThinLTOCodeGenerator::run() {
|
||||
// Sequential linking phase
|
||||
auto Index = linkCombinedIndex();
|
||||
|
||||
// Save temps: index.
|
||||
if (!SaveTempsDir.empty()) {
|
||||
auto SaveTempPath = SaveTempsDir + "index.bc";
|
||||
std::error_code EC;
|
||||
raw_fd_ostream OS(SaveTempPath, EC, sys::fs::F_None);
|
||||
if (EC)
|
||||
report_fatal_error(Twine("Failed to open ") + SaveTempPath +
|
||||
" to save optimized bitcode\n");
|
||||
WriteFunctionSummaryToFile(*Index, OS);
|
||||
}
|
||||
|
||||
// Prepare the resulting object vector
|
||||
assert(ProducedBinaries.empty() && "The generator should not be reused");
|
||||
ProducedBinaries.resize(Modules.size());
|
||||
|
||||
// Prepare the module map.
|
||||
auto ModuleMap = generateModuleMap(Modules);
|
||||
|
||||
// Parallel optimizer + codegen
|
||||
{
|
||||
ThreadPool Pool(ThreadCount);
|
||||
int count = 0;
|
||||
for (auto &ModuleBuffer : Modules) {
|
||||
Pool.async([&](int count) {
|
||||
LLVMContext Context;
|
||||
|
||||
// Parse module now
|
||||
auto TheModule = loadModuleFromBuffer(ModuleBuffer, Context, false);
|
||||
|
||||
// Save temps: original file.
|
||||
if (!SaveTempsDir.empty()) {
|
||||
saveTempBitcode(*TheModule, SaveTempsDir, count, ".0.original.bc");
|
||||
}
|
||||
|
||||
ProducedBinaries[count] = ProcessThinLTOModule(
|
||||
*TheModule, *Index, ModuleMap, *TMBuilder.create(), CacheOptions,
|
||||
SaveTempsDir, count);
|
||||
}, count);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// If statistics were requested, print them out now.
|
||||
if (llvm::AreStatisticsEnabled())
|
||||
llvm::PrintStatistics();
|
||||
}
|
32
test/ThinLTO/Inputs/funcimport.ll
Normal file
32
test/ThinLTO/Inputs/funcimport.ll
Normal file
@ -0,0 +1,32 @@
|
||||
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-apple-macosx10.11.0"
|
||||
|
||||
|
||||
define i32 @main() #0 {
|
||||
entry:
|
||||
call void (...) @weakalias()
|
||||
call void (...) @analias()
|
||||
%call = call i32 (...) @referencestatics()
|
||||
%call1 = call i32 (...) @referenceglobals()
|
||||
%call2 = call i32 (...) @referencecommon()
|
||||
call void (...) @setfuncptr()
|
||||
call void (...) @callfuncptr()
|
||||
call void (...) @callweakfunc()
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
declare void @weakalias(...) #1
|
||||
|
||||
declare void @analias(...) #1
|
||||
|
||||
declare i32 @referencestatics(...) #1
|
||||
|
||||
declare i32 @referenceglobals(...) #1
|
||||
|
||||
declare i32 @referencecommon(...) #1
|
||||
|
||||
declare void @setfuncptr(...) #1
|
||||
|
||||
declare void @callfuncptr(...) #1
|
||||
|
||||
declare void @callweakfunc(...) #1
|
139
test/ThinLTO/funcimport.ll
Normal file
139
test/ThinLTO/funcimport.ll
Normal file
@ -0,0 +1,139 @@
|
||||
; Do setup work for all below tests: generate bitcode and combined index
|
||||
; RUN: llvm-as -function-summary %s -o %t.bc
|
||||
; RUN: llvm-as -function-summary %p/Inputs/funcimport.ll -o %t2.bc
|
||||
; RUN: llvm-lto -thinlto-action=thinlink -o %t3.bc %t.bc %t2.bc
|
||||
|
||||
; Ensure statics are promoted/renamed correctly from this file (all but
|
||||
; constant variable need promotion).
|
||||
; RUN: llvm-lto -thinlto-action=promote %t.bc -thinlto-index=%t3.bc -o - | llvm-dis -o - | FileCheck %s --check-prefix=EXPORTSTATIC
|
||||
; EXPORTSTATIC-DAG: @staticvar.llvm.0 = hidden global
|
||||
; EXPORTSTATIC-DAG: @staticconstvar = internal unnamed_addr constant
|
||||
; EXPORTSTATIC-DAG: @P.llvm.0 = hidden global void ()* null
|
||||
; EXPORTSTATIC-DAG: define hidden i32 @staticfunc.llvm.0
|
||||
; EXPORTSTATIC-DAG: define hidden void @staticfunc2.llvm.0
|
||||
|
||||
; Ensure that both weak alias to an imported function and strong alias to a
|
||||
; non-imported function are correctly turned into declarations.
|
||||
; Also ensures that alias to a linkonce function is turned into a declaration
|
||||
; and that the associated linkonce function is not in the output, as it is
|
||||
; lazily linked and never referenced/materialized.
|
||||
; RUN: llvm-lto -thinlto-action=import %t2.bc -thinlto-index=%t3.bc -o - | llvm-dis -o - | FileCheck %s --check-prefix=IMPORTGLOB1
|
||||
; IMPORTGLOB1-DAG: define available_externally void @globalfunc1
|
||||
; IMPORTGLOB1-DAG: declare void @weakalias
|
||||
; IMPORTGLOB1-DAG: declare void @analias
|
||||
; IMPORTGLOB1-NOT: @linkoncealias
|
||||
; IMPORTGLOB1-NOT: @linkoncefunc
|
||||
; IMPORTGLOB1-NOT: declare void @globalfunc2
|
||||
|
||||
; Verify that the optimizer run
|
||||
; RUN: llvm-lto -thinlto-action=optimize %t2.bc -o - | llvm-dis -o - | FileCheck %s --check-prefix=OPTIMIZED
|
||||
; OPTIMIZED: define i32 @main()
|
||||
|
||||
; Verify that the codegen run
|
||||
; RUN: llvm-lto -thinlto-action=codegen %t2.bc -o - | llvm-nm -o - | FileCheck %s --check-prefix=CODEGEN
|
||||
; CODEGEN: T _main
|
||||
|
||||
; Verify that all run together
|
||||
; RUN: llvm-lto -thinlto-action=run %t2.bc %t.bc
|
||||
; RUN: llvm-nm -o - < %t.bc.thinlto.o | FileCheck %s --check-prefix=ALL
|
||||
; RUN: llvm-nm -o - < %t2.bc.thinlto.o | FileCheck %s --check-prefix=ALL2
|
||||
; ALL: T _callfuncptr
|
||||
; ALL2: T _main
|
||||
|
||||
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-apple-macosx10.11.0"
|
||||
|
||||
@globalvar_in_section = global i32 1, align 4
|
||||
@globalvar = global i32 1, align 4
|
||||
@staticvar = internal global i32 1, align 4
|
||||
@staticvar2 = internal global i32 1, align 4
|
||||
@staticconstvar = internal unnamed_addr constant [2 x i32] [i32 10, i32 20], align 4
|
||||
@commonvar = common global i32 0, align 4
|
||||
@P = internal global void ()* null, align 8
|
||||
|
||||
@weakalias = weak alias void (...), bitcast (void ()* @globalfunc1 to void (...)*)
|
||||
@analias = alias void (...), bitcast (void ()* @globalfunc2 to void (...)*)
|
||||
@linkoncealias = alias void (...), bitcast (void ()* @linkoncefunc to void (...)*)
|
||||
|
||||
define void @globalfunc1() #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @globalfunc2() #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define linkonce_odr void @linkoncefunc() #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define i32 @referencestatics(i32 %i) #0 {
|
||||
entry:
|
||||
%i.addr = alloca i32, align 4
|
||||
store i32 %i, i32* %i.addr, align 4
|
||||
%call = call i32 @staticfunc()
|
||||
%0 = load i32, i32* @staticvar, align 4
|
||||
%add = add nsw i32 %call, %0
|
||||
%1 = load i32, i32* %i.addr, align 4
|
||||
%idxprom = sext i32 %1 to i64
|
||||
%arrayidx = getelementptr inbounds [2 x i32], [2 x i32]* @staticconstvar, i64 0, i64 %idxprom
|
||||
%2 = load i32, i32* %arrayidx, align 4
|
||||
%add1 = add nsw i32 %add, %2
|
||||
ret i32 %add1
|
||||
}
|
||||
|
||||
define i32 @referenceglobals(i32 %i) #0 {
|
||||
entry:
|
||||
%i.addr = alloca i32, align 4
|
||||
store i32 %i, i32* %i.addr, align 4
|
||||
call void @globalfunc1()
|
||||
%0 = load i32, i32* @globalvar, align 4
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
define i32 @referencecommon(i32 %i) #0 {
|
||||
entry:
|
||||
%i.addr = alloca i32, align 4
|
||||
store i32 %i, i32* %i.addr, align 4
|
||||
%0 = load i32, i32* @commonvar, align 4
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
define void @setfuncptr() #0 {
|
||||
entry:
|
||||
store void ()* @staticfunc2, void ()** @P, align 8
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @callfuncptr() #0 {
|
||||
entry:
|
||||
%0 = load void ()*, void ()** @P, align 8
|
||||
call void %0()
|
||||
ret void
|
||||
}
|
||||
|
||||
@weakvar = weak global i32 1, align 4
|
||||
define weak void @weakfunc() #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @callweakfunc() #0 {
|
||||
entry:
|
||||
call void @weakfunc()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal i32 @staticfunc() #0 {
|
||||
entry:
|
||||
ret i32 1
|
||||
}
|
||||
|
||||
define internal void @staticfunc2() #0 {
|
||||
entry:
|
||||
%0 = load i32, i32* @staticvar2, align 4
|
||||
ret void
|
||||
}
|
@ -17,7 +17,9 @@
|
||||
#include "llvm/CodeGen/CommandFlags.h"
|
||||
#include "llvm/IR/DiagnosticPrinter.h"
|
||||
#include "llvm/IR/LLVMContext.h"
|
||||
#include "llvm/IRReader/IRReader.h"
|
||||
#include "llvm/LTO/LTOCodeGenerator.h"
|
||||
#include "llvm/LTO/ThinLTOCodeGenerator.h"
|
||||
#include "llvm/LTO/LTOModule.h"
|
||||
#include "llvm/Object/FunctionIndexObjectFile.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
@ -25,6 +27,7 @@
|
||||
#include "llvm/Support/ManagedStatic.h"
|
||||
#include "llvm/Support/PrettyStackTrace.h"
|
||||
#include "llvm/Support/Signals.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
#include "llvm/Support/TargetSelect.h"
|
||||
#include "llvm/Support/ToolOutputFile.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
@ -64,6 +67,36 @@ static cl::opt<bool>
|
||||
ThinLTO("thinlto", cl::init(false),
|
||||
cl::desc("Only write combined global index for ThinLTO backends"));
|
||||
|
||||
enum ThinLTOModes {
|
||||
THINLINK,
|
||||
THINPROMOTE,
|
||||
THINIMPORT,
|
||||
THINOPT,
|
||||
THINCODEGEN,
|
||||
THINALL
|
||||
};
|
||||
|
||||
cl::opt<ThinLTOModes> ThinLTOMode(
|
||||
"thinlto-action", cl::desc("Perform a single ThinLTO stage:"),
|
||||
cl::values(
|
||||
clEnumValN(
|
||||
THINLINK, "thinlink",
|
||||
"ThinLink: produces the index by linking only the summaries."),
|
||||
clEnumValN(THINPROMOTE, "promote",
|
||||
"Perform pre-import promotion (requires -thinlto-index)."),
|
||||
clEnumValN(THINIMPORT, "import", "Perform both promotion and "
|
||||
"cross-module importing (requires "
|
||||
"-thinlto-index)."),
|
||||
clEnumValN(THINOPT, "optimize", "Perform ThinLTO optimizations."),
|
||||
clEnumValN(THINCODEGEN, "codegen", "CodeGen (expected to match llc)"),
|
||||
clEnumValN(THINALL, "run", "Perform ThinLTO end-to-end"),
|
||||
clEnumValEnd));
|
||||
|
||||
static cl::opt<std::string>
|
||||
ThinLTOIndex("thinlto-index",
|
||||
cl::desc("Provide the index produced by a ThinLink, required "
|
||||
"to perform the promotion and/or importing."));
|
||||
|
||||
static cl::opt<bool>
|
||||
SaveModuleFile("save-merged-module", cl::init(false),
|
||||
cl::desc("Write merged LTO module to file before CodeGen"));
|
||||
@ -241,6 +274,255 @@ static void createCombinedFunctionIndex() {
|
||||
OS.close();
|
||||
}
|
||||
|
||||
namespace thinlto {
|
||||
|
||||
std::vector<std::unique_ptr<MemoryBuffer>>
|
||||
loadAllFilesForIndex(const FunctionInfoIndex &Index) {
|
||||
std::vector<std::unique_ptr<MemoryBuffer>> InputBuffers;
|
||||
|
||||
for (auto &ModPath : Index.modPathStringEntries()) {
|
||||
const auto &Filename = ModPath.first();
|
||||
auto CurrentActivity = "loading file '" + Filename + "'";
|
||||
auto InputOrErr = MemoryBuffer::getFile(Filename);
|
||||
error(InputOrErr, "error " + CurrentActivity);
|
||||
InputBuffers.push_back(std::move(*InputOrErr));
|
||||
}
|
||||
return InputBuffers;
|
||||
}
|
||||
|
||||
std::unique_ptr<FunctionInfoIndex> loadCombinedIndex() {
|
||||
if (ThinLTOIndex.empty())
|
||||
report_fatal_error("Missing -thinlto-index for ThinLTO promotion stage");
|
||||
auto CurrentActivity = "loading file '" + ThinLTOIndex + "'";
|
||||
ErrorOr<std::unique_ptr<FunctionInfoIndex>> IndexOrErr =
|
||||
llvm::getFunctionIndexForFile(ThinLTOIndex, diagnosticHandler);
|
||||
error(IndexOrErr, "error " + CurrentActivity);
|
||||
return std::move(IndexOrErr.get());
|
||||
}
|
||||
|
||||
static std::unique_ptr<Module> loadModule(StringRef Filename,
|
||||
LLVMContext &Ctx) {
|
||||
SMDiagnostic Err;
|
||||
std::unique_ptr<Module> M(parseIRFile(Filename, Err, Ctx));
|
||||
if (!M) {
|
||||
Err.print("llvm-lto", errs());
|
||||
report_fatal_error("Can't load module for file " + Filename);
|
||||
}
|
||||
return M;
|
||||
}
|
||||
|
||||
static void writeModuleToFile(Module &TheModule, StringRef Filename) {
|
||||
std::error_code EC;
|
||||
raw_fd_ostream OS(Filename, EC, sys::fs::OpenFlags::F_None);
|
||||
error(EC, "error opening the file '" + Filename + "'");
|
||||
WriteBitcodeToFile(&TheModule, OS, true, false);
|
||||
}
|
||||
|
||||
class ThinLTOProcessing {
|
||||
public:
|
||||
ThinLTOCodeGenerator ThinGenerator;
|
||||
|
||||
ThinLTOProcessing(const TargetOptions &Options) {
|
||||
ThinGenerator.setCodePICModel(RelocModel);
|
||||
ThinGenerator.setTargetOptions(Options);
|
||||
}
|
||||
|
||||
void run() {
|
||||
switch (ThinLTOMode) {
|
||||
case THINLINK:
|
||||
return thinLink();
|
||||
case THINPROMOTE:
|
||||
return promote();
|
||||
case THINIMPORT:
|
||||
return import();
|
||||
case THINOPT:
|
||||
return optimize();
|
||||
case THINCODEGEN:
|
||||
return codegen();
|
||||
case THINALL:
|
||||
return runAll();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// Load the input files, create the combined index, and write it out.
|
||||
void thinLink() {
|
||||
// Perform "ThinLink": just produce the index
|
||||
if (OutputFilename.empty())
|
||||
report_fatal_error(
|
||||
"OutputFilename is necessary to store the combined index.\n");
|
||||
|
||||
LLVMContext Ctx;
|
||||
std::vector<std::unique_ptr<MemoryBuffer>> InputBuffers;
|
||||
for (unsigned i = 0; i < InputFilenames.size(); ++i) {
|
||||
auto &Filename = InputFilenames[i];
|
||||
StringRef CurrentActivity = "loading file '" + Filename + "'";
|
||||
auto InputOrErr = MemoryBuffer::getFile(Filename);
|
||||
error(InputOrErr, "error " + CurrentActivity);
|
||||
InputBuffers.push_back(std::move(*InputOrErr));
|
||||
ThinGenerator.addModule(Filename, InputBuffers.back()->getBuffer());
|
||||
}
|
||||
|
||||
auto CombinedIndex = ThinGenerator.linkCombinedIndex();
|
||||
std::error_code EC;
|
||||
raw_fd_ostream OS(OutputFilename, EC, sys::fs::OpenFlags::F_None);
|
||||
error(EC, "error opening the file '" + OutputFilename + "'");
|
||||
WriteFunctionSummaryToFile(*CombinedIndex, OS);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Load the combined index from disk, then load every file referenced by
|
||||
/// the index and add them to the generator, finally perform the promotion
|
||||
/// on the files mentioned on the command line (these must match the index
|
||||
/// content).
|
||||
void promote() {
|
||||
if (InputFilenames.size() != 1 && !OutputFilename.empty())
|
||||
report_fatal_error("Can't handle a single output filename and multiple "
|
||||
"input files, do not provide an output filename and "
|
||||
"the output files will be suffixed from the input "
|
||||
"ones.");
|
||||
|
||||
auto Index = loadCombinedIndex();
|
||||
for (auto &Filename : InputFilenames) {
|
||||
LLVMContext Ctx;
|
||||
auto TheModule = loadModule(Filename, Ctx);
|
||||
|
||||
ThinGenerator.promote(*TheModule, *Index);
|
||||
|
||||
std::string OutputName = OutputFilename;
|
||||
if (OutputName.empty()) {
|
||||
OutputName = Filename + ".thinlto.promoted.bc";
|
||||
}
|
||||
writeModuleToFile(*TheModule, OutputName);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the combined index from disk, then load every file referenced by
|
||||
/// the index and add them to the generator, then performs the promotion and
|
||||
/// cross module importing on the files mentioned on the command line
|
||||
/// (these must match the index content).
|
||||
void import() {
|
||||
if (InputFilenames.size() != 1 && !OutputFilename.empty())
|
||||
report_fatal_error("Can't handle a single output filename and multiple "
|
||||
"input files, do not provide an output filename and "
|
||||
"the output files will be suffixed from the input "
|
||||
"ones.");
|
||||
|
||||
auto Index = loadCombinedIndex();
|
||||
auto InputBuffers = loadAllFilesForIndex(*Index);
|
||||
for (auto &MemBuffer : InputBuffers)
|
||||
ThinGenerator.addModule(MemBuffer->getBufferIdentifier(),
|
||||
MemBuffer->getBuffer());
|
||||
|
||||
for (auto &Filename : InputFilenames) {
|
||||
LLVMContext Ctx;
|
||||
auto TheModule = loadModule(Filename, Ctx);
|
||||
|
||||
ThinGenerator.crossModuleImport(*TheModule, *Index);
|
||||
|
||||
std::string OutputName = OutputFilename;
|
||||
if (OutputName.empty()) {
|
||||
OutputName = Filename + ".thinlto.imported.bc";
|
||||
}
|
||||
writeModuleToFile(*TheModule, OutputName);
|
||||
}
|
||||
}
|
||||
|
||||
void optimize() {
|
||||
if (InputFilenames.size() != 1 && !OutputFilename.empty())
|
||||
report_fatal_error("Can't handle a single output filename and multiple "
|
||||
"input files, do not provide an output filename and "
|
||||
"the output files will be suffixed from the input "
|
||||
"ones.");
|
||||
if (!ThinLTOIndex.empty())
|
||||
errs() << "Warning: -thinlto-index ignored for optimize stage";
|
||||
|
||||
for (auto &Filename : InputFilenames) {
|
||||
LLVMContext Ctx;
|
||||
auto TheModule = loadModule(Filename, Ctx);
|
||||
|
||||
ThinGenerator.optimize(*TheModule);
|
||||
|
||||
std::string OutputName = OutputFilename;
|
||||
if (OutputName.empty()) {
|
||||
OutputName = Filename + ".thinlto.imported.bc";
|
||||
}
|
||||
writeModuleToFile(*TheModule, OutputName);
|
||||
}
|
||||
}
|
||||
|
||||
void codegen() {
|
||||
if (InputFilenames.size() != 1 && !OutputFilename.empty())
|
||||
report_fatal_error("Can't handle a single output filename and multiple "
|
||||
"input files, do not provide an output filename and "
|
||||
"the output files will be suffixed from the input "
|
||||
"ones.");
|
||||
if (!ThinLTOIndex.empty())
|
||||
errs() << "Warning: -thinlto-index ignored for codegen stage";
|
||||
|
||||
for (auto &Filename : InputFilenames) {
|
||||
LLVMContext Ctx;
|
||||
auto TheModule = loadModule(Filename, Ctx);
|
||||
|
||||
auto Buffer = ThinGenerator.codegen(*TheModule);
|
||||
std::string OutputName = OutputFilename;
|
||||
if (OutputName.empty()) {
|
||||
OutputName = Filename + ".thinlto.o";
|
||||
}
|
||||
if (OutputName == "-") {
|
||||
outs() << Buffer->getBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code EC;
|
||||
raw_fd_ostream OS(OutputName, EC, sys::fs::OpenFlags::F_None);
|
||||
error(EC, "error opening the file '" + OutputName + "'");
|
||||
OS << Buffer->getBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
/// Full ThinLTO process
|
||||
void runAll() {
|
||||
if (!OutputFilename.empty())
|
||||
report_fatal_error("Do not provide an output filename for ThinLTO "
|
||||
" processing, the output files will be suffixed from "
|
||||
"the input ones.");
|
||||
|
||||
if (!ThinLTOIndex.empty())
|
||||
errs() << "Warning: -thinlto-index ignored for full ThinLTO process";
|
||||
|
||||
LLVMContext Ctx;
|
||||
std::vector<std::unique_ptr<MemoryBuffer>> InputBuffers;
|
||||
for (unsigned i = 0; i < InputFilenames.size(); ++i) {
|
||||
auto &Filename = InputFilenames[i];
|
||||
StringRef CurrentActivity = "loading file '" + Filename + "'";
|
||||
auto InputOrErr = MemoryBuffer::getFile(Filename);
|
||||
error(InputOrErr, "error " + CurrentActivity);
|
||||
InputBuffers.push_back(std::move(*InputOrErr));
|
||||
ThinGenerator.addModule(Filename, InputBuffers.back()->getBuffer());
|
||||
}
|
||||
|
||||
ThinGenerator.run();
|
||||
|
||||
auto &Binaries = ThinGenerator.getProducedBinaries();
|
||||
if (Binaries.size() != InputFilenames.size())
|
||||
report_fatal_error("Number of output objects does not match the number "
|
||||
"of inputs");
|
||||
|
||||
for (unsigned BufID = 0; BufID < Binaries.size(); ++BufID) {
|
||||
auto OutputName = InputFilenames[BufID] + ".thinlto.o";
|
||||
std::error_code EC;
|
||||
raw_fd_ostream OS(OutputName, EC, sys::fs::OpenFlags::F_None);
|
||||
error(EC, "error opening the file '" + OutputName + "'");
|
||||
OS << Binaries[BufID]->getBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the combined index from disk, then load every file referenced by
|
||||
};
|
||||
|
||||
} // namespace thinlto
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Print a stack trace if we signal out.
|
||||
sys::PrintStackTraceOnErrorSignal();
|
||||
@ -266,6 +548,14 @@ int main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ThinLTOMode.getNumOccurrences()) {
|
||||
if (ThinLTOMode.getNumOccurrences() > 1)
|
||||
report_fatal_error("You can't specify more than one -thinlto-action");
|
||||
thinlto::ThinLTOProcessing ThinLTOProcessor(Options);
|
||||
ThinLTOProcessor.run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ThinLTO) {
|
||||
createCombinedFunctionIndex();
|
||||
return 0;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "llvm/IR/LLVMContext.h"
|
||||
#include "llvm/LTO/LTOCodeGenerator.h"
|
||||
#include "llvm/LTO/LTOModule.h"
|
||||
#include "llvm/LTO/ThinLTOCodeGenerator.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Signals.h"
|
||||
#include "llvm/Support/TargetSelect.h"
|
||||
@ -134,6 +135,7 @@ struct LibLTOCodeGenerator : LTOCodeGenerator {
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(LibLTOCodeGenerator, lto_code_gen_t)
|
||||
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(ThinLTOCodeGenerator, thinlto_code_gen_t)
|
||||
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(LTOModule, lto_module_t)
|
||||
|
||||
// Convert the subtarget features into a string to pass to LTOCodeGenerator.
|
||||
@ -440,3 +442,106 @@ void lto_codegen_set_should_embed_uselists(lto_code_gen_t cg,
|
||||
lto_bool_t ShouldEmbedUselists) {
|
||||
unwrap(cg)->setShouldEmbedUselists(ShouldEmbedUselists);
|
||||
}
|
||||
|
||||
// ThinLTO API below
|
||||
|
||||
thinlto_code_gen_t thinlto_create_codegen() {
|
||||
lto_initialize();
|
||||
ThinLTOCodeGenerator *CodeGen = new ThinLTOCodeGenerator();
|
||||
CodeGen->setTargetOptions(InitTargetOptionsFromCodeGenFlags());
|
||||
|
||||
return wrap(CodeGen);
|
||||
}
|
||||
|
||||
void thinlto_codegen_dispose(thinlto_code_gen_t cg) { delete unwrap(cg); }
|
||||
|
||||
void thinlto_codegen_add_module(thinlto_code_gen_t cg, const char *Identifier,
|
||||
const char *Data, int Length) {
|
||||
unwrap(cg)->addModule(Identifier, StringRef(Data, Length));
|
||||
}
|
||||
|
||||
void thinlto_codegen_process(thinlto_code_gen_t cg) { unwrap(cg)->run(); }
|
||||
|
||||
unsigned int thinlto_module_get_num_objects(thinlto_code_gen_t cg) {
|
||||
return unwrap(cg)->getProducedBinaries().size();
|
||||
}
|
||||
LTOObjectBuffer thinlto_module_get_object(thinlto_code_gen_t cg,
|
||||
unsigned int index) {
|
||||
assert(index < unwrap(cg)->getProducedBinaries().size() && "Index overflow");
|
||||
auto &MemBuffer = unwrap(cg)->getProducedBinaries()[index];
|
||||
return LTOObjectBuffer{(void *)MemBuffer->getBufferStart(),
|
||||
MemBuffer->getBufferSize()};
|
||||
}
|
||||
|
||||
void thinlto_debug_options(const char *const *options, int number) {
|
||||
// if options were requested, set them
|
||||
if (number && options) {
|
||||
std::vector<const char *> CodegenArgv(1, "libLTO");
|
||||
for (auto Arg : ArrayRef<const char *>(options, number))
|
||||
CodegenArgv.push_back(Arg);
|
||||
cl::ParseCommandLineOptions(CodegenArgv.size(), CodegenArgv.data());
|
||||
}
|
||||
}
|
||||
|
||||
bool lto_module_is_thinlto(lto_module_t mod) {
|
||||
return unwrap(mod)->isThinLTO();
|
||||
}
|
||||
|
||||
void thinlto_codegen_add_must_preserve_symbol(thinlto_code_gen_t cg,
|
||||
const char *Name, int Length) {
|
||||
unwrap(cg)->preserveSymbol(StringRef(Name, Length));
|
||||
}
|
||||
|
||||
void thinlto_codegen_add_cross_referenced_symbol(thinlto_code_gen_t cg,
|
||||
const char *Name, int Length) {
|
||||
unwrap(cg)->crossReferenceSymbol(StringRef(Name, Length));
|
||||
}
|
||||
|
||||
void thinlto_codegen_set_cpu(thinlto_code_gen_t cg, const char *cpu) {
|
||||
return unwrap(cg)->setCpu(cpu);
|
||||
}
|
||||
|
||||
void thinlto_codegen_set_cache_dir(thinlto_code_gen_t cg,
|
||||
const char *cache_dir) {
|
||||
return unwrap(cg)->setCacheDir(cache_dir);
|
||||
}
|
||||
|
||||
void thinlto_codegen_set_cache_pruning_interval(thinlto_code_gen_t cg,
|
||||
int interval) {
|
||||
return unwrap(cg)->setCachePruningInterval(interval);
|
||||
}
|
||||
|
||||
void thinlto_codegen_set_cache_entry_expiration(thinlto_code_gen_t cg,
|
||||
unsigned expiration) {
|
||||
return unwrap(cg)->setCacheEntryExpiration(expiration);
|
||||
}
|
||||
|
||||
void thinlto_codegen_set_final_cache_size_relative_to_available_space(
|
||||
thinlto_code_gen_t cg, unsigned Percentage) {
|
||||
return unwrap(cg)->setMaxCacheSizeRelativeToAvailableSpace(Percentage);
|
||||
}
|
||||
|
||||
void thinlto_codegen_set_savetemps_dir(thinlto_code_gen_t cg,
|
||||
const char *save_temps_dir) {
|
||||
return unwrap(cg)->setSaveTempsDir(save_temps_dir);
|
||||
}
|
||||
|
||||
lto_bool_t thinlto_codegen_set_pic_model(thinlto_code_gen_t cg,
|
||||
lto_codegen_model model) {
|
||||
switch (model) {
|
||||
case LTO_CODEGEN_PIC_MODEL_STATIC:
|
||||
unwrap(cg)->setCodePICModel(Reloc::Static);
|
||||
return false;
|
||||
case LTO_CODEGEN_PIC_MODEL_DYNAMIC:
|
||||
unwrap(cg)->setCodePICModel(Reloc::PIC_);
|
||||
return false;
|
||||
case LTO_CODEGEN_PIC_MODEL_DYNAMIC_NO_PIC:
|
||||
unwrap(cg)->setCodePICModel(Reloc::DynamicNoPIC);
|
||||
return false;
|
||||
case LTO_CODEGEN_PIC_MODEL_DEFAULT:
|
||||
unwrap(cg)->setCodePICModel(Reloc::Default);
|
||||
return false;
|
||||
}
|
||||
sLastErrorString = "Unknown PIC model";
|
||||
return true;
|
||||
}
|
||||
|
@ -45,3 +45,20 @@ LLVMCreateDisasmCPU
|
||||
LLVMDisasmDispose
|
||||
LLVMDisasmInstruction
|
||||
LLVMSetDisasmOptions
|
||||
thinlto_create_codegen
|
||||
thinlto_codegen_dispose
|
||||
thinlto_codegen_add_module
|
||||
thinlto_codegen_process
|
||||
thinlto_module_get_num_objects
|
||||
thinlto_module_get_object
|
||||
thinlto_codegen_set_pic_model
|
||||
thinlto_codegen_set_cache_dir
|
||||
thinlto_codegen_set_cache_pruning_interval
|
||||
thinlto_codegen_set_cache_entry_expiration
|
||||
thinlto_codegen_set_savetemps_dir
|
||||
thinlto_codegen_set_cpu
|
||||
thinlto_debug_options
|
||||
lto_module_is_thinlto
|
||||
thinlto_codegen_add_must_preserve_symbol
|
||||
thinlto_codegen_add_cross_referenced_symbol
|
||||
thinlto_codegen_set_final_cache_size_relative_to_available_space
|
Loading…
Reference in New Issue
Block a user