1 #include "LLVM_Output.h"
2 #include "CodeGen_C.h"
3 #include "CodeGen_Internal.h"
4 #include "CodeGen_LLVM.h"
5 #include "CompilerLogger.h"
6 #include "LLVM_Headers.h"
7 #include "LLVM_Runtime_Linker.h"
8 
9 #include <fstream>
10 #include <iostream>
11 
12 #ifdef _WIN32
13 #ifndef NOMINMAX
14 #define NOMINMAX
15 #endif
16 #include <windows.h>
17 #else
18 #include <stdio.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 #endif
22 
23 namespace Halide {
24 
25 namespace Internal {
26 namespace Archive {
27 
28 // This is a bare-bones Windows .lib file writer, based on inspection
29 // of the LLVM ArchiveWriter class and the documentation at
30 // https://www.microsoft.com/msj/0498/hood0498.aspx and
31 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx#archive__library__file_format
32 //
33 // It has been compared with the output of VS2015's lib.exe and appears to be
34 // bit-identical (to meaningful bits, anyway) for a sampling of Halide
35 // AOT output, but it is quite possible that there are omissions, mistakes,
36 // or just plain bugs.
37 
38 // Emit a field that is 'size' characters wide.
39 // If data too small, pad on the right with spaces.
40 // If data too large, assert.
41 // Return the offset at which 'data' was written.
42 template<typename T>
emit_padded(std::ostream & out,T data,size_t size)43 size_t emit_padded(std::ostream &out, T data, size_t size) {
44     size_t pos = out.tellp();
45     out << data;
46     size_t written = (size_t)out.tellp() - pos;
47     internal_assert(written <= size);
48     while (written < size) {
49         out.put(' ');
50         written++;
51     }
52     return pos;
53 }
54 
55 using EmitU32 = std::function<void(std::ostream &, uint32_t)>;
56 
emit_big_endian_u32(std::ostream & out,uint32_t value)57 void emit_big_endian_u32(std::ostream &out, uint32_t value) {
58     out << static_cast<uint8_t>((value >> 24) & 0xff)
59         << static_cast<uint8_t>((value >> 16) & 0xff)
60         << static_cast<uint8_t>((value >> 8) & 0xff)
61         << static_cast<uint8_t>((value)&0xff);
62 }
63 
emit_little_endian_u32(std::ostream & out,uint32_t value)64 void emit_little_endian_u32(std::ostream &out, uint32_t value) {
65     out << static_cast<uint8_t>((value)&0xff)
66         << static_cast<uint8_t>((value >> 8) & 0xff)
67         << static_cast<uint8_t>((value >> 16) & 0xff)
68         << static_cast<uint8_t>((value >> 24) & 0xff);
69 }
70 
emit_little_endian_u16(std::ostream & out,uint16_t value)71 void emit_little_endian_u16(std::ostream &out, uint16_t value) {
72     out << static_cast<uint8_t>((value)&0xff)
73         << static_cast<uint8_t>((value >> 8) & 0xff);
74 }
75 
76 // Return the offset at which 'size' was written
finish_member_header(std::ostream & out,size_t size)77 size_t finish_member_header(std::ostream &out, size_t size) {
78     // Emit zero for all of these, to mimic the 'deterministic' flag
79     emit_padded(out, 0, 12);                        // timestamp
80     emit_padded(out, ' ', 6);                       // UID
81     emit_padded(out, ' ', 6);                       // GID
82     emit_padded(out, 0, 8);                         // perm
83     const size_t pos = emit_padded(out, size, 10);  // total size of the archive member (not including header)
84     out << "\x60\x0A";
85     return pos;
86 }
87 
member_name(const llvm::NewArchiveMember & m)88 std::string member_name(const llvm::NewArchiveMember &m) {
89     return m.MemberName.str();
90 }
91 
write_string_table(std::ostream & out,const std::vector<llvm::NewArchiveMember> & members)92 std::map<std::string, size_t> write_string_table(std::ostream &out,
93                                                  const std::vector<llvm::NewArchiveMember> &members) {
94     std::map<std::string, size_t> string_to_offset_map;
95     size_t start_offset = 0;
96     for (const llvm::NewArchiveMember &m : members) {
97         std::string name = member_name(m);
98         internal_assert(string_to_offset_map.count(name) == 0);
99         if (name.size() < 16 && name.find('/') == std::string::npos) {
100             // small strings that don't contain '/' can be inlined
101             continue;
102         }
103         if (start_offset == 0) {
104             emit_padded(out, "//", 16);
105             finish_member_header(out, 0);
106             start_offset = out.tellp();
107         }
108         string_to_offset_map[name] = (size_t)out.tellp() - start_offset;
109         out << name;
110         out.put('\0');
111     }
112     // If all strings are short enough, we skip the string table entirely
113     if (start_offset != 0) {
114         size_t member_end = out.tellp();
115         if (out.tellp() % 2) {
116             out.put('\x0A');
117         }
118         size_t final_offset = out.tellp();
119         out.seekp(start_offset - 12);
120         emit_padded(out, member_end - start_offset, 10);
121         out.seekp(final_offset);
122     }
123     return string_to_offset_map;
124 }
125 
126 struct PatchInfo {
127     EmitU32 emit_u32;
128     size_t pos;
129 };
130 
write_symbol_table(std::ostream & out,const std::vector<llvm::NewArchiveMember> & members,bool windows_coff_format,std::map<size_t,std::vector<PatchInfo>> * patchers)131 void write_symbol_table(std::ostream &out,
132                         const std::vector<llvm::NewArchiveMember> &members,
133                         bool windows_coff_format,
134                         std::map<size_t, std::vector<PatchInfo>> *patchers) {
135     internal_assert(!members.empty());
136 
137     EmitU32 emit_u32 = windows_coff_format ? emit_little_endian_u32 : emit_big_endian_u32;
138 
139     // Write zero for sizes/offsets that will be patched later.
140     const size_t kPatchLater = 0;
141 
142     std::map<std::string, size_t> name_to_member_index;
143 
144     const auto kFileMagicUnknown = llvm::file_magic::unknown;
145 
146     llvm::LLVMContext context;
147     for (size_t i = 0, n = members.size(); i < n; ++i) {
148         llvm::MemoryBufferRef member_buffer = members[i].Buf->getMemBufferRef();
149         llvm::Expected<std::unique_ptr<llvm::object::SymbolicFile>> obj_or_err =
150             llvm::object::SymbolicFile::createSymbolicFile(
151                 member_buffer, kFileMagicUnknown, &context);
152         if (!obj_or_err) {
153             // Don't use internal_assert: the call to new_member.takeError() will be
154             // evaluated even if the assert does not fail, leaving new_member in an
155             // indeterminate state.
156             internal_error << llvm::toString(obj_or_err.takeError()) << "\n";
157         }
158         llvm::object::SymbolicFile &obj = *obj_or_err.get();
159         for (const auto &sym : obj.symbols()) {
160 #if LLVM_VERSION >= 110
161             auto flags = sym.getFlags();
162             if (!flags) {
163                 internal_error << llvm::toString(flags.takeError()) << "\n";
164             }
165             const uint32_t sym_flags = flags.get();
166 #else
167             const uint32_t sym_flags = sym.getFlags();
168 #endif
169             if (sym_flags & llvm::object::SymbolRef::SF_FormatSpecific) {
170                 continue;
171             }
172             if (!(sym_flags & llvm::object::SymbolRef::SF_Global)) {
173                 continue;
174             }
175             if ((sym_flags & llvm::object::SymbolRef::SF_Undefined) &&
176                 !(sym_flags & llvm::object::SymbolRef::SF_Indirect)) {
177                 continue;
178             }
179             // Windows COFF doesn't support weak symbols.
180             if (sym_flags & llvm::object::SymbolRef::SF_Weak) {
181                 continue;
182             }
183 
184             llvm::SmallString<128> symbols_buf;
185             llvm::raw_svector_ostream symbols(symbols_buf);
186             auto err = sym.printName(symbols);
187             internal_assert(!err);
188             std::string name = symbols.str().str();
189             if (name_to_member_index.find(name) != name_to_member_index.end()) {
190                 user_warning << "Warning: symbol '" << name << "' seen multiple times in library.\n";
191                 continue;
192             }
193             name_to_member_index[name] = i;
194         }
195     }
196 
197     size_t header_start_offset = emit_padded(out, "/", 16);
198     size_t symbol_table_size_offset = finish_member_header(out, kPatchLater);  // size of symbol table
199 
200     size_t symbol_count_offset = 0;
201     if (windows_coff_format) {
202         emit_u32(out, members.size());
203         for (size_t i = 0, n = members.size(); i < n; ++i) {
204             size_t pos = out.tellp();
205             emit_u32(out, kPatchLater);  // offset to this .obj member
206             (*patchers)[i].push_back({emit_u32, pos});
207         }
208         symbol_count_offset = out.tellp();
209         emit_u32(out, kPatchLater);  // number of symbols
210         // symbol-to-archive-member-index, but 1-based rather than zero-based.
211         for (auto &it : name_to_member_index) {
212             internal_assert(it.second <= 65534);
213             emit_little_endian_u16(out, (uint16_t)it.second + 1);
214         }
215     } else {
216         symbol_count_offset = out.tellp();
217         emit_u32(out, kPatchLater);  // number of symbols
218         for (auto &it : name_to_member_index) {
219             size_t pos = out.tellp();
220             emit_u32(out, kPatchLater);  // offset to the .obj member containing this symbol
221             (*patchers)[it.second].push_back({emit_u32, pos});
222         }
223     }
224 
225     // Symbol table goes at the end for both variants.
226     for (auto &it : name_to_member_index) {
227         out << it.first;
228         out.put('\0');
229     }
230 
231     size_t member_end = out.tellp();
232 
233     // lib.exe pads to 2-byte align with 0x0a
234     if (out.tellp() % 2) {
235         out.put('\x0A');
236     }
237     size_t final_offset = out.tellp();
238 
239     // Patch the size of the symbol table.
240     const size_t member_header_size = 60;
241     out.seekp(symbol_table_size_offset);
242     emit_padded(out, member_end - member_header_size - header_start_offset, 10);
243 
244     // Patch the number of symbols.
245     out.seekp(symbol_count_offset);
246     emit_u32(out, name_to_member_index.size());
247 
248     // Seek back to where we left off.
249     out.seekp(final_offset);
250 }
251 
write_coff_archive(std::ostream & out,const std::vector<llvm::NewArchiveMember> & members)252 void write_coff_archive(std::ostream &out,
253                         const std::vector<llvm::NewArchiveMember> &members) {
254     out << "!<arch>\x0A";
255 
256     // First member is named "/" and is the traditional symbol table,
257     // with big-endian offsets.
258     std::map<size_t, std::vector<PatchInfo>> patchers;
259     write_symbol_table(out, members, false, &patchers);
260 
261     // Second member (for Windows COFF) is also named "/" and is also a symbol table,
262     // but with little-endian offsets and with symbols sorted by name. (We actually sort
263     // both tables as a side-effect, but the first isn't required to be sorted.)
264     write_symbol_table(out, members, true, &patchers);
265 
266     // Third member, named "//", is the optional string table. (MS docs say it is required but
267     // lib.exe only emits as needed, so we will follow its example)
268     std::map<std::string, size_t> string_to_offset_map = write_string_table(out, members);
269 
270     // The remaining members are just (header + contents of .obj file).
271     std::vector<size_t> member_offset;
272     for (const llvm::NewArchiveMember &m : members) {
273         size_t pos = out.tellp();
274         member_offset.push_back(pos);
275 
276         std::string name = member_name(m);
277         auto it = string_to_offset_map.find(name);
278         if (it != string_to_offset_map.end()) {
279             out.put('/');
280             emit_padded(out, it->second, 15);
281         } else {
282             emit_padded(out, name + "/", 16);
283         }
284         size_t size = m.Buf->getBufferSize();
285         finish_member_header(out, size);
286 
287         out << m.Buf->getMemBufferRef().getBuffer().str();
288 
289         if (out.tellp() % 2) {
290             out.put('\x0A');
291         }
292     }
293 
294     for (auto &it : patchers) {
295         size_t i = it.first;
296         for (auto &patcher : it.second) {
297             out.seekp(patcher.pos);
298             patcher.emit_u32(out, member_offset.at(i));
299         }
300     }
301 }
302 
303 }  // namespace Archive
304 }  // namespace Internal
305 
make_raw_fd_ostream(const std::string & filename)306 std::unique_ptr<llvm::raw_fd_ostream> make_raw_fd_ostream(const std::string &filename) {
307     std::string error_string;
308     std::error_code err;
309     std::unique_ptr<llvm::raw_fd_ostream> raw_out(new llvm::raw_fd_ostream(filename, err, llvm::sys::fs::F_None));
310     if (err) error_string = err.message();
311     internal_assert(error_string.empty())
312         << "Error opening output " << filename << ": " << error_string << "\n";
313 
314     return raw_out;
315 }
316 
317 namespace {
318 
319 // llvm::CloneModule has issues with debug info. As a workaround,
320 // serialize it to bitcode in memory, and then parse the bitcode back in.
clone_module(const llvm::Module & module_in)321 std::unique_ptr<llvm::Module> clone_module(const llvm::Module &module_in) {
322     Internal::debug(2) << "Cloning module " << module_in.getName().str() << "\n";
323 
324     // Write the module to a buffer.
325     llvm::SmallVector<char, 16> clone_buffer;
326     llvm::raw_svector_ostream clone_ostream(clone_buffer);
327     WriteBitcodeToFile(module_in, clone_ostream);
328 
329     // Read it back in.
330     llvm::MemoryBufferRef buffer_ref(llvm::StringRef(clone_buffer.data(), clone_buffer.size()), "clone_buffer");
331     auto cloned_module = llvm::parseBitcodeFile(buffer_ref, module_in.getContext());
332     internal_assert(cloned_module);
333 
334     return std::move(cloned_module.get());
335 }
336 
337 }  // namespace
338 
emit_file(const llvm::Module & module_in,Internal::LLVMOStream & out,llvm::CodeGenFileType file_type)339 void emit_file(const llvm::Module &module_in, Internal::LLVMOStream &out,
340 #if LLVM_VERSION >= 100
341                llvm::CodeGenFileType file_type
342 #else
343                llvm::TargetMachine::CodeGenFileType file_type
344 #endif
345 ) {
346     Internal::debug(1) << "emit_file.Compiling to native code...\n";
347     Internal::debug(2) << "Target triple: " << module_in.getTargetTriple() << "\n";
348 
349     auto time_start = std::chrono::high_resolution_clock::now();
350 
351     // Work on a copy of the module to avoid modifying the original.
352     std::unique_ptr<llvm::Module> module = clone_module(module_in);
353 
354     // Get the target specific parser.
355     auto target_machine = Internal::make_target_machine(*module);
356     internal_assert(target_machine.get()) << "Could not allocate target machine!\n";
357 
358     llvm::DataLayout target_data_layout(target_machine->createDataLayout());
359     if (!(target_data_layout == module->getDataLayout())) {
360         internal_error << "Warning: module's data layout does not match target machine's\n"
361                        << target_data_layout.getStringRepresentation() << "\n"
362                        << module->getDataLayout().getStringRepresentation() << "\n";
363     }
364 
365     // Build up all of the passes that we want to do to the module.
366     llvm::legacy::PassManager pass_manager;
367 
368     pass_manager.add(new llvm::TargetLibraryInfoWrapperPass(llvm::Triple(module->getTargetTriple())));
369 
370     // Make sure things marked as always-inline get inlined
371     pass_manager.add(llvm::createAlwaysInlinerLegacyPass());
372 
373     // Remove any stale debug info
374     pass_manager.add(llvm::createStripDeadDebugInfoPass());
375 
376     // Enable symbol rewriting. This allows code outside libHalide to
377     // use symbol rewriting when compiling Halide code (for example, by
378     // using cl::ParseCommandLineOption and then passing the appropriate
379     // rewrite options via -mllvm flags).
380     pass_manager.add(llvm::createRewriteSymbolsPass());
381 
382     // Override default to generate verbose assembly.
383     target_machine->Options.MCOptions.AsmVerbose = true;
384 
385     // Ask the target to add backend passes as necessary.
386     target_machine->addPassesToEmitFile(pass_manager, out, nullptr, file_type);
387 
388     pass_manager.run(*module);
389 
390     auto *logger = Internal::get_compiler_logger();
391     if (logger) {
392         auto time_end = std::chrono::high_resolution_clock::now();
393         std::chrono::duration<double> diff = time_end - time_start;
394         logger->record_compilation_time(Internal::CompilerLogger::Phase::LLVM, diff.count());
395     }
396 
397     // If -time-passes is in HL_LLVM_ARGS, this will print llvm passes time statstics otherwise its no-op.
398     llvm::reportAndResetTimings();
399 }
400 
compile_module_to_llvm_module(const Module & module,llvm::LLVMContext & context)401 std::unique_ptr<llvm::Module> compile_module_to_llvm_module(const Module &module, llvm::LLVMContext &context) {
402     return codegen_llvm(module, context);
403 }
404 
compile_llvm_module_to_object(llvm::Module & module,Internal::LLVMOStream & out)405 void compile_llvm_module_to_object(llvm::Module &module, Internal::LLVMOStream &out) {
406 #if LLVM_VERSION >= 100
407     emit_file(module, out, llvm::CGFT_ObjectFile);
408 #else
409     emit_file(module, out, llvm::TargetMachine::CGFT_ObjectFile);
410 #endif
411 }
412 
compile_llvm_module_to_assembly(llvm::Module & module,Internal::LLVMOStream & out)413 void compile_llvm_module_to_assembly(llvm::Module &module, Internal::LLVMOStream &out) {
414 #if LLVM_VERSION >= 100
415     emit_file(module, out, llvm::CGFT_AssemblyFile);
416 #else
417     emit_file(module, out, llvm::TargetMachine::CGFT_AssemblyFile);
418 #endif
419 }
420 
compile_llvm_module_to_llvm_bitcode(llvm::Module & module,Internal::LLVMOStream & out)421 void compile_llvm_module_to_llvm_bitcode(llvm::Module &module, Internal::LLVMOStream &out) {
422     WriteBitcodeToFile(module, out);
423 }
424 
compile_llvm_module_to_llvm_assembly(llvm::Module & module,Internal::LLVMOStream & out)425 void compile_llvm_module_to_llvm_assembly(llvm::Module &module, Internal::LLVMOStream &out) {
426     module.print(out, nullptr);
427 }
428 
429 // Note that the utilities for get/set working directory are deliberately *not* in Util.h;
430 // generally speaking, you shouldn't ever need or want to do this, and doing so is asking for
431 // trouble. This exists solely to work around an issue with LLVM, hence its restricted
432 // location. If we ever legitimately need this elsewhere, consider moving it to Util.h.
433 namespace {
434 
get_current_directory()435 std::string get_current_directory() {
436 #ifdef _WIN32
437     DWORD dir_buf_size = GetCurrentDirectoryW(0, nullptr);
438     internal_assert(dir_buf_size) << "GetCurrentDirectoryW() failed; error " << GetLastError() << "\n";
439 
440     // GetCurrentDirectoryW returns a _buffer size_, not a character count.
441     // std::wstring null-terminates on its own, so don't count that here.
442     std::wstring wdir(dir_buf_size - 1, 0);
443 
444     DWORD ret = GetCurrentDirectoryW(dir_buf_size, &wdir[0]);
445     internal_assert(ret) << "GetCurrentDirectoryW() failed; error " << GetLastError() << "\n";
446 
447     int dir_len = WideCharToMultiByte(CP_UTF8, 0, &wdir[0], (int)wdir.size(), nullptr, 0, nullptr, nullptr);
448     internal_assert(dir_len) << "WideCharToMultiByte() failed; error " << GetLastError() << "\n";
449 
450     std::string dir(dir_len, 0);
451 
452     ret = WideCharToMultiByte(CP_UTF8, 0, &wdir[0], (int)wdir.size(), &dir[0], (int)dir.size(), nullptr, nullptr);
453     internal_assert(ret) << "WideCharToMultiByte() failed; error " << GetLastError() << "\n";
454 
455     return dir;
456 #else
457     std::string dir;
458     // Note that passing null for the first arg isn't strictly POSIX, but is
459     // supported everywhere we currently build.
460     char *p = getcwd(nullptr, 0);
461     internal_assert(p != NULL) << "getcwd() failed";
462     dir = p;
463     free(p);
464     return dir;
465 #endif
466 }
467 
set_current_directory(const std::string & d)468 void set_current_directory(const std::string &d) {
469 #ifdef _WIN32
470     int n_chars = MultiByteToWideChar(CP_UTF8, 0, &d[0], (int)d.size(), nullptr, 0);
471     internal_assert(n_chars) << "MultiByteToWideChar() failed; error " << GetLastError() << "\n";
472 
473     std::wstring wd(n_chars, 0);
474     int ret = MultiByteToWideChar(CP_UTF8, 0, &d[0], (int)d.size(), &wd[0], wd.size());
475     internal_assert(ret) << "MultiByteToWideChar() failed; error " << GetLastError() << "\n";
476 
477     internal_assert(SetCurrentDirectoryW(wd.c_str())) << "SetCurrentDirectoryW() failed; error " << GetLastError() << "\n";
478 #else
479     internal_assert(chdir(d.c_str()) == 0) << "chdir() failed";
480 #endif
481 }
482 
dir_and_file(const std::string & path)483 std::pair<std::string, std::string> dir_and_file(const std::string &path) {
484     std::string dir, file;
485     size_t slash_pos = path.rfind('/');
486 #ifdef _WIN32
487     if (slash_pos == std::string::npos) {
488         // Windows is a thing
489         slash_pos = path.rfind('\\');
490     }
491 #endif
492     if (slash_pos != std::string::npos) {
493         dir = path.substr(0, slash_pos);
494         file = path.substr(slash_pos + 1);
495     } else {
496         file = path;
497     }
498     return {dir, file};
499 }
500 
make_absolute_path(const std::string & path)501 std::string make_absolute_path(const std::string &path) {
502     bool is_absolute = !path.empty() && path[0] == '/';
503     char sep = '/';
504 #ifdef _WIN32
505     // Allow for C:\whatever or c:/whatever on Windows
506     if (path.size() >= 3 && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) {
507         is_absolute = true;
508         sep = path[2];
509     } else if (path.size() > 2 && path[0] == '\\' && path[1] == '\\') {
510         // Also allow for UNC-style paths beginning with double-backslash
511         is_absolute = true;
512         sep = path[0];
513     }
514 #endif
515     if (!is_absolute) {
516         return get_current_directory() + sep + path;
517     }
518     return path;
519 }
520 
521 struct SetCwd {
522     const std::string original_directory;
SetCwdHalide::__anonccc398140211::SetCwd523     explicit SetCwd(const std::string &d)
524         : original_directory(get_current_directory()) {
525         if (!d.empty()) {
526             set_current_directory(d);
527         }
528     }
~SetCwdHalide::__anonccc398140211::SetCwd529     ~SetCwd() {
530         set_current_directory(original_directory);
531     }
532 };
533 
534 }  // namespace
535 
create_static_library(const std::vector<std::string> & src_files_in,const Target & target,const std::string & dst_file_in,bool deterministic)536 void create_static_library(const std::vector<std::string> &src_files_in, const Target &target,
537                            const std::string &dst_file_in, bool deterministic) {
538     internal_assert(!src_files_in.empty());
539 
540     // Ensure that dst_file is an absolute path, since we're going to change the
541     // working directory temporarily.
542     std::string dst_file = make_absolute_path(dst_file_in);
543 
544     // If we give absolute paths to LLVM, it will dutifully embed them in the resulting
545     // .a file; some versions of 'ar x' are unable to deal with the resulting files,
546     // which is inconvenient. So let's doctor the inputs to be simple filenames,
547     // and temporarily change the working directory. (Note that this requires all the
548     // input files be in the same directory; this is currently always the case for
549     // our existing usage.)
550     std::string src_dir = dir_and_file(src_files_in.front()).first;
551     std::vector<std::string> src_files;
552     for (auto &s_in : src_files_in) {
553         auto df = dir_and_file(s_in);
554         internal_assert(df.first == src_dir) << "All inputs to create_static_library() must be in the same directory";
555         for (auto &s_existing : src_files) {
556             internal_assert(s_existing != df.second) << "create_static_library() does not allow duplicate filenames.";
557         }
558         src_files.push_back(df.second);
559     }
560 
561     SetCwd set_cwd(src_dir);
562 
563     std::vector<llvm::NewArchiveMember> new_members;
564     for (auto &src : src_files) {
565         llvm::Expected<llvm::NewArchiveMember> new_member =
566             llvm::NewArchiveMember::getFile(src, /*Deterministic=*/true);
567         if (!new_member) {
568             // Don't use internal_assert: the call to new_member.takeError() will be evaluated
569             // even if the assert does not fail, leaving new_member in an indeterminate
570             // state.
571             internal_error << src << ": " << llvm::toString(new_member.takeError()) << "\n";
572         }
573         new_members.push_back(std::move(*new_member));
574     }
575 
576     // LLVM can't write MS PE/COFF Lib format, which is almost-but-not-quite
577     // the same as GNU ar format.
578     if (Internal::get_triple_for_target(target).isWindowsMSVCEnvironment()) {
579         std::ofstream f(dst_file, std::ios_base::trunc | std::ios_base::binary);
580         Internal::Archive::write_coff_archive(f, new_members);
581         f.flush();
582         f.close();
583         return;
584     }
585 
586     const bool write_symtab = true;
587     const auto kind = Internal::get_triple_for_target(target).isOSDarwin() ? llvm::object::Archive::K_BSD : llvm::object::Archive::K_GNU;
588     const bool thin = false;
589     auto result = llvm::writeArchive(dst_file, new_members,
590                                      write_symtab, kind,
591                                      deterministic, thin, nullptr);
592     internal_assert(!result)
593         << "Failed to write archive: " << dst_file
594         << ", reason: " << llvm::toString(std::move(result)) << "\n";
595 }
596 
597 }  // namespace Halide
598