+// Create the ThinLTO codegenerator
+thinlto_code_gen_t Parser::init_thinlto_codegen(const std::vector<File*>& files,
+ const std::vector<const ld::Atom*>& allAtoms,
+ ld::Internal& state,
+ const OptimizeOptions& options,
+ CStringToAtom& deadllvmAtoms,
+ CStringToAtom& llvmAtoms) {
+ const bool logMustPreserve = false;
+
+ thinlto_code_gen_t thingenerator = ::thinlto_create_codegen();
+
+ // Caching control
+ if (options.ltoCachePath && !options.bitcodeBundle) {
+ struct stat statBuffer;
+ if( stat(options.ltoCachePath, &statBuffer) != 0 || !S_ISDIR(statBuffer.st_mode) ) {
+ if ( mkdir(options.ltoCachePath, 0700) !=0 ) {
+ warning("unable to create ThinLTO cache directory: %s", options.ltoCachePath);
+ }
+ }
+ thinlto_codegen_set_cache_dir(thingenerator, options.ltoCachePath);
+ thinlto_codegen_set_cache_pruning_interval(thingenerator, options.ltoPruneInterval);
+ thinlto_codegen_set_cache_entry_expiration(thingenerator, options.ltoPruneAfter);
+ thinlto_codegen_set_final_cache_size_relative_to_available_space(thingenerator, options.ltoMaxCacheSize);
+ }
+
+ // if requested, ask the code generator to save off intermediate bitcode files
+ if ( options.saveTemps ) {
+ std::string tempPath = options.outputFilePath;
+ tempPath += ".thinlto.bcs/";
+ struct stat statBuffer;
+ if( stat(tempPath.c_str(), &statBuffer) != 0 || !S_ISDIR(statBuffer.st_mode) ) {
+ if ( mkdir(tempPath.c_str(), 0700) !=0 ) {
+ warning("unable to create ThinLTO output directory for temporary bitcode files: %s", tempPath.c_str());
+ }
+ }
+ thinlto_codegen_set_savetemps_dir(thingenerator, tempPath.c_str());
+ }
+
+ // Set some codegen options
+ if ( thinlto_codegen_set_pic_model(thingenerator, getCodeModel(options)) )
+ throwf("could not create set codegen model: %s", lto_get_error_message());
+
+ // Expose reachability informations for internalization in LTO
+
+ // The atom graph uses directed edges (references). Collect all references where
+ // originating atom is not part of any LTO Reader. This allows optimizer to optimize an
+ // external (i.e. not originated from same .o file) reference if all originating atoms are also
+ // defined in llvm bitcode file.
+ CStringSet nonLLVMRefs;
+ CStringSet LLVMRefs;
+ for (std::vector<const ld::Atom*>::const_iterator it = allAtoms.begin(); it != allAtoms.end(); ++it) {
+ const ld::Atom* atom = *it;
+ const ld::Atom* target;
+ for (ld::Fixup::iterator fit=atom->fixupsBegin(); fit != atom->fixupsEnd(); ++fit) {
+ switch ( fit->binding ) {
+ case ld::Fixup::bindingDirectlyBound:
+ // that reference a ThinLTO llvm atom
+ target = fit->u.target;
+ if ( target->contentType() == ld::Atom::typeLTOtemporary &&
+ ((lto::File *)target->file())->isThinLTO() &&
+ atom->file() != target->file()
+ ) {
+ if (atom->contentType() != ld::Atom::typeLTOtemporary ||
+ !((lto::File *)atom->file())->isThinLTO())
+ nonLLVMRefs.insert(target->name());
+ else
+ LLVMRefs.insert(target->name());
+ if ( logMustPreserve )
+ fprintf(stderr, "Found a reference from %s -> %s\n", atom->name(), target->name());
+ }
+ break;
+ case ld::Fixup::bindingsIndirectlyBound:
+ target = state.indirectBindingTable[fit->u.bindingIndex];
+ if ( (target != NULL) && (target->contentType() == ld::Atom::typeLTOtemporary) &&
+ ((lto::File *)target->file())->isThinLTO() &&
+ atom->file() != target->file()
+ ) {
+ if (atom->contentType() != ld::Atom::typeLTOtemporary ||
+ !((lto::File *)atom->file())->isThinLTO())
+ nonLLVMRefs.insert(target->name());
+ else
+ LLVMRefs.insert(target->name());
+ if ( logMustPreserve )
+ fprintf(stderr, "Found a reference from %s -> %s\n", atom->name(), target->name());
+ }
+ default:
+ break;
+ }
+ }
+ if (atom->contentType() == ld::Atom::typeLTOtemporary &&
+ ((lto::File *)atom->file())->isThinLTO()) {
+ llvmAtoms[atom->name()] = (Atom*)atom;
+ }
+ }
+ // if entry point is in a llvm bitcode file, it must be preserved by LTO
+ if ( state.entryPoint != NULL ) {
+ if ( state.entryPoint->contentType() == ld::Atom::typeLTOtemporary )
+ nonLLVMRefs.insert(state.entryPoint->name());
+ }
+ for (auto file : files) {
+ for(uint32_t i=0; i < file->_atomArrayCount; ++i) {
+ Atom* llvmAtom = &file->_atomArray[i];
+ if ( llvmAtom->coalescedAway() ) {
+ const char* name = llvmAtom->name();
+ if ( deadllvmAtoms.find(name) == deadllvmAtoms.end() ) {
+ if ( logMustPreserve )
+ fprintf(stderr, "lto_codegen_add_must_preserve_symbol(%s) because linker coalesce away and replace with a mach-o atom\n", name);
+ ::thinlto_codegen_add_must_preserve_symbol(thingenerator, name, strlen(name));
+ deadllvmAtoms[name] = (Atom*)llvmAtom;
+ }
+ }
+ else if ( options.linkerDeadStripping && !llvmAtom->live() ) {
+ const char* name = llvmAtom->name();
+ deadllvmAtoms[name] = (Atom*)llvmAtom;
+ }
+ }
+ }
+
+ // tell code generator about symbols that must be preserved
+ for (CStringToAtom::iterator it = llvmAtoms.begin(); it != llvmAtoms.end(); ++it) {
+ const char* name = it->first;
+ Atom* atom = it->second;
+ // Include llvm Symbol in export list if it meets one of following two conditions
+ // 1 - atom scope is global (and not linkage unit).
+ // 2 - included in nonLLVMRefs set.
+ // If a symbol is not listed in exportList then LTO is free to optimize it away.
+ if ( (atom->scope() == ld::Atom::scopeGlobal) && options.preserveAllGlobals ) {
+ if ( logMustPreserve ) fprintf(stderr, "lto_codegen_add_must_preserve_symbol(%s) because global symbol\n", name);
+ ::thinlto_codegen_add_must_preserve_symbol(thingenerator, name, strlen(name));
+ }
+ else if ( nonLLVMRefs.find(name) != nonLLVMRefs.end() ) {
+ if ( logMustPreserve ) fprintf(stderr, "lto_codegen_add_must_preserve_symbol(%s) because referenced from outside of ThinLTO\n", name);
+ ::thinlto_codegen_add_must_preserve_symbol(thingenerator, name, strlen(name));
+ }
+ else if ( LLVMRefs.find(name) != LLVMRefs.end() ) {
+ if ( logMustPreserve ) fprintf(stderr, "lto_codegen_add_must_preserve_symbol(%s) because referenced from another file\n", name);
+ ::thinlto_codegen_add_cross_referenced_symbol(thingenerator, name, strlen(name));
+ } else {
+ if ( logMustPreserve ) fprintf(stderr, "NOT preserving(%s)\n", name);
+ }
+// FIXME: to be implemented
+// else if ( options.relocatable && hasNonllvmAtoms ) {
+// // <rdar://problem/14334895> ld -r mode but merging in some mach-o files, so need to keep libLTO from optimizing away anything
+// if ( logMustPreserve ) fprintf(stderr, "lto_codegen_add_must_preserve_symbol(%s) because -r mode disable LTO dead stripping\n", name);
+// ::thinlto_codegen_add_must_preserve_symbol(thingenerator, name, strlen(name));
+// }
+ }
+
+ return thingenerator;
+}
+
+// Full LTO processing
+bool Parser::optimizeThinLTO(const std::vector<File*>& files,
+ const std::vector<const ld::Atom*>& allAtoms,
+ ld::Internal& state,
+ const OptimizeOptions& options,
+ ld::File::AtomHandler& handler,
+ std::vector<const ld::Atom*>& newAtoms,
+ std::vector<const char*>& additionalUndefines) {
+ const bool logBitcodeFiles = false;
+
+ if (files.empty())
+ return true;
+
+#if LTO_API_VERSION >= 18
+
+ if (::lto_api_version() < 18)
+ throwf("lto: could not use -thinlto because libLTO is too old (version '%d', >=18 is required)", ::lto_api_version());
+
+ // Handle -mllvm options
+ if ( !_s_llvmOptionsProcessed ) {
+ thinlto_debug_options(options.llvmOptions->data(), options.llvmOptions->size());
+ _s_llvmOptionsProcessed = true;
+ }
+
+ // Create the ThinLTO codegenerator
+ CStringToAtom deadllvmAtoms;
+ CStringToAtom llvmAtoms;
+ thinlto_code_gen_t thingenerator = init_thinlto_codegen(files, allAtoms, state, options, deadllvmAtoms, llvmAtoms);
+
+
+ ld::File::Ordinal lastOrdinal;
+ for (auto *f : files) {
+ if ( logBitcodeFiles) fprintf(stderr, "thinlto_codegen_add_module(%s)\n", f->path());
+ f->addToThinGenerator(thingenerator);
+ lastOrdinal = f->ordinal();
+ }
+
+#if LTO_API_VERSION >= 19
+ // In the bitcode bundle case, we first run the generator with codegen disabled
+ // and get the bitcode output. These files are added for later bundling, and a
+ // new codegenerator is setup with these as input, and the optimizer disabled.
+ if (options.bitcodeBundle) {
+ // Bitcode Bundle case
+ thinlto_codegen_disable_codegen(thingenerator, true);
+ // Process the optimizer only
+ thinlto_codegen_process(thingenerator);
+ auto numObjects = thinlto_module_get_num_objects(thingenerator);
+ // Save the codegenerator
+ thinlto_code_gen_t bitcode_generator = thingenerator;
+ // Create a new codegen generator for the codegen part.
+ thingenerator = init_thinlto_codegen(files, allAtoms, state, options, deadllvmAtoms, llvmAtoms);
+ // Disable the optimizer
+ thinlto_codegen_set_codegen_only(thingenerator, true);
+
+ // Save bitcode files for later, and add them to the codegen generator.
+ for (unsigned bufID = 0; bufID < numObjects; ++bufID) {
+ auto machOFile = thinlto_module_get_object(bitcode_generator, bufID);
+ std::string tempMachoPath = options.outputFilePath;
+ tempMachoPath += ".";
+ tempMachoPath += std::to_string(bufID);
+ tempMachoPath += ".thinlto.o.bc";
+ state.ltoBitcodePath.push_back(tempMachoPath);
+ int fd = ::open(tempMachoPath.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if ( fd != -1 ) {
+ ::write(fd, machOFile.Buffer, machOFile.Size);
+ ::close(fd);
+ } else {
+ throwf("unable to write temporary ThinLTO output: %s", tempMachoPath.c_str());
+ }
+
+ // Add the optimized bitcode to the codegen generator now.
+ ::thinlto_codegen_add_module(thingenerator, tempMachoPath.c_str(), (const char *)machOFile.Buffer, machOFile.Size);
+ }
+ }
+
+ if (options.ltoCodegenOnly)
+ // Disable the optimizer
+ thinlto_codegen_set_codegen_only(thingenerator, true);
+#endif
+
+ // run code generator
+ thinlto_codegen_process(thingenerator);
+ auto numObjects = thinlto_module_get_num_objects(thingenerator);
+ if (!numObjects)
+ throwf("could not do ThinLTO codegen (thinlto_codegen_process didn't produce any object): '%s', using libLTO version '%s'", ::lto_get_error_message(), ::lto_get_version());
+
+ // if requested, save off objects files
+ if ( options.saveTemps ) {
+ for (unsigned bufID = 0; bufID < numObjects; ++bufID) {
+ auto machOFile = thinlto_module_get_object(thingenerator, bufID);
+ std::string tempMachoPath = options.outputFilePath;
+ tempMachoPath += ".";
+ tempMachoPath += std::to_string(bufID);
+ tempMachoPath += ".thinlto.o";
+ int fd = ::open(tempMachoPath.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if ( fd != -1 ) {
+ ::write(fd, machOFile.Buffer, machOFile.Size);
+ ::close(fd);
+ } else {
+ warning("unable to write temporary ThinLTO output: %s", tempMachoPath.c_str());
+ }
+ }
+ }
+
+
+ // mach-o parsing is done in-memory, but need path for debug notes
+ std::string macho_dirpath = "/tmp/thinlto.o";
+ if ( options.tmpObjectFilePath != NULL ) {
+ macho_dirpath = options.tmpObjectFilePath;
+ struct stat statBuffer;
+ if( stat(macho_dirpath.c_str(), &statBuffer) != 0 || !S_ISDIR(statBuffer.st_mode) ) {
+ if ( mkdir(macho_dirpath.c_str(), 0700) !=0 ) {
+ warning("unable to create ThinLTO output directory for temporary object files: %s", macho_dirpath.c_str());
+ }
+ }
+ }
+
+ auto ordinal = ld::File::Ordinal::LTOOrdinal().nextFileListOrdinal();
+ for (unsigned bufID = 0; bufID < numObjects; ++bufID) {
+ auto machOFile = thinlto_module_get_object(thingenerator, bufID);
+ if (!machOFile.Size) {
+ warning("Ignoring empty buffer generated by ThinLTO");
+ continue;
+ }
+
+ // mach-o parsing is done in-memory, but need path for debug notes
+ std::string tmp_path = macho_dirpath + "/" + std::to_string(bufID) + ".o";
+
+ // parse generated mach-o file into a MachOReader
+ ld::relocatable::File* machoFile = parseMachOFile((const uint8_t *)machOFile.Buffer, machOFile.Size, tmp_path, options, ordinal);
+ ordinal = ordinal.nextFileListOrdinal();
+
+ // if needed, save temp mach-o file to specific location
+ if ( options.tmpObjectFilePath != NULL ) {
+ int fd = ::open(tmp_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if ( fd != -1) {
+ ::write(fd, (const uint8_t *)machOFile.Buffer, machOFile.Size);
+ ::close(fd);
+ }
+ else {
+ warning("could not write ThinLTO temp file '%s', errno=%d", tmp_path.c_str(), errno);
+ }
+ }
+
+ // Load the generated MachO file
+ loadMachO(machoFile, options, handler, newAtoms, additionalUndefines, llvmAtoms, deadllvmAtoms);
+ }
+
+ // Remove Atoms from ld if code generator optimized them away
+ for (CStringToAtom::iterator li = llvmAtoms.begin(), le = llvmAtoms.end(); li != le; ++li) {
+ // check if setRealAtom() called on this Atom
+ if ( li->second->compiledAtom() == NULL ) {
+ //fprintf(stderr, "llvm optimized away %p %s\n", li->second, li->second->name());
+ li->second->setCoalescedAway();
+ }
+ }
+
+ return true;
+#else // ! (LTO_API_VERSION >= 18)
+ throwf("lto: could not use -thinlto because ld was built against a version of libLTO too old (version '%d', >=18 is required)", LTO_API_VERSION);
+#endif
+}
+
+bool Parser::optimize( const std::vector<const ld::Atom*>& allAtoms,
+ ld::Internal& state,
+ const OptimizeOptions& options,
+ ld::File::AtomHandler& handler,
+ std::vector<const ld::Atom*>& newAtoms,
+ std::vector<const char*>& additionalUndefines)
+{
+
+ // exit quickly if nothing to do
+ if ( _s_files.size() == 0 )
+ return false;
+
+ // print out LTO version string if -v was used
+ if ( options.verbose )
+ fprintf(stderr, "%s\n", ::lto_get_version());
+
+ // <rdar://problem/12379604> The order that files are merged must match command line order
+ std::sort(_s_files.begin(), _s_files.end(), CommandLineOrderFileSorter());
+
+#if LTO_API_VERSION >= 19
+ // If ltoCodegenOnly is set, we don't want to merge any bitcode files and perform FullLTO
+ // we just take the ThinLTO path (optimization will be disabled anyway).
+ if (options.ltoCodegenOnly) {
+ for (auto *file : _s_files) {
+ file->setIsThinLTO(true);
+ }
+ }
+#endif
+
+ std::vector<File *> theLTOFiles;
+ std::vector<File *> theThinLTOFiles;
+ for (auto *file : _s_files) {
+ if (file->isThinLTO()) {
+ theThinLTOFiles.push_back(file);
+ } else {
+ theLTOFiles.push_back(file);
+ }
+ }
+
+ auto result = optimizeThinLTO(theThinLTOFiles, allAtoms, state, options, handler, newAtoms, additionalUndefines) &&
+ optimizeLTO(theLTOFiles, allAtoms, state, options, handler, newAtoms, additionalUndefines);
+
+ // Remove InternalAtoms from ld
+ for (std::vector<File*>::iterator it=_s_files.begin(); it != _s_files.end(); ++it) {
+ (*it)->internalAtom().setCoalescedAway();
+ }
+
+ return result;
+}
+