1const std = @import("std");
2const assert = std.debug.assert;
3
4const Compilation = @import("../Compilation.zig");
5const build_options = @import("build_options");
6const trace = @import("../tracy.zig").trace;
7const Module = @import("../Module.zig");
8
9pub const BuildError = error{
10 OutOfMemory,
11 AlreadyReported,
12 ZigCompilerNotBuiltWithLLVMExtensions,
13 TSANUnsupportedCPUArchitecture,
14} || std.Io.Cancelable;
15
16pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!void {
17 if (!build_options.have_llvm) {
18 return error.ZigCompilerNotBuiltWithLLVMExtensions;
19 }
20
21 const tracy = trace(@src());
22 defer tracy.end();
23
24 var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
25 defer arena_allocator.deinit();
26 const arena = arena_allocator.allocator();
27
28 const io = comp.io;
29 const target = comp.getTarget();
30 const root_name = switch (target.os.tag) {
31 // On Apple platforms, we use the same name as LLVM because the
32 // TSAN library implementation hard-codes a check for these names.
33 .driverkit, .maccatalyst, .macos => "clang_rt.tsan_osx_dynamic",
34 .ios => if (target.abi == .simulator) "clang_rt.tsan_iossim_dynamic" else "clang_rt.tsan_ios_dynamic",
35 .tvos => if (target.abi == .simulator) "clang_rt.tsan_tvossim_dynamic" else "clang_rt.tsan_tvos_dynamic",
36 .visionos => if (target.abi == .simulator) "clang_rt.tsan_xrossim_dynamic" else "clang_rt.tsan_xros_dynamic",
37 .watchos => if (target.abi == .simulator) "clang_rt.tsan_watchossim_dynamic" else "clang_rt.tsan_watchos_dynamic",
38 else => "tsan",
39 };
40 const link_mode: std.lang.LinkMode = if (target.os.tag.isDarwin()) .dynamic else .static;
41 const output_mode = .Lib;
42 const basename = try std.zig.binNameAlloc(arena, .{
43 .root_name = root_name,
44 .cpu_arch = target.cpu.arch,
45 .os_tag = target.os.tag,
46 .ofmt = target.ofmt,
47 .abi = target.abi,
48 .output_mode = output_mode,
49 .link_mode = link_mode,
50 });
51
52 const optimize_mode = comp.compilerRtOptMode();
53 const strip = comp.compilerRtStrip();
54 const unwind_tables: std.lang.UnwindTables =
55 if (target.cpu.arch == .x86 and target.os.tag == .windows) .none else .async;
56 const link_libcpp = target.os.tag.isDarwin();
57
58 const config = Compilation.Config.resolve(.{
59 .output_mode = output_mode,
60 .link_mode = link_mode,
61 .resolved_target = comp.root_mod.resolved_target,
62 .is_test = false,
63 .have_zcu = false,
64 .emit_bin = true,
65 .root_optimize_mode = optimize_mode,
66 .root_strip = strip,
67 .link_libc = true,
68 .link_libcpp = link_libcpp,
69 .any_unwind_tables = unwind_tables != .none,
70 // LLVM disables LTO for its libtsan.
71 .lto = .none,
72 }) catch |err| {
73 comp.lockAndSetMiscFailure(
74 .libtsan,
75 "unable to build thread sanitizer runtime: resolving configuration failed: {s}",
76 .{@errorName(err)},
77 );
78 return error.AlreadyReported;
79 };
80
81 const common_flags = [_][]const u8{
82 "-DTSAN_CONTAINS_UBSAN=0",
83 };
84
85 const root_mod = Module.create(arena, .{
86 .paths = .{
87 .root = .zig_lib_root,
88 .root_src_path = "",
89 },
90 .fully_qualified_name = "root",
91 .inherited = .{
92 .resolved_target = comp.root_mod.resolved_target,
93 .strip = strip,
94 .stack_check = false,
95 .stack_protector = 0,
96 .sanitize_c = .off,
97 .sanitize_thread = false,
98 .red_zone = comp.root_mod.red_zone,
99 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
100 .valgrind = false,
101 .unwind_tables = unwind_tables,
102 .optimize_mode = optimize_mode,
103 .pic = true,
104 .no_builtin = true,
105 .code_model = comp.root_mod.code_model,
106 },
107 .global = config,
108 .cc_argv = &common_flags,
109 .parent = null,
110 }) catch |err| {
111 comp.lockAndSetMiscFailure(
112 .libtsan,
113 "unable to build thread sanitizer runtime: creating module failed: {s}",
114 .{@errorName(err)},
115 );
116 return error.AlreadyReported;
117 };
118
119 var c_source_files = std.array_list.Managed(Compilation.CSourceFile).init(arena);
120 try c_source_files.ensureUnusedCapacity(tsan_sources.len);
121
122 const tsan_include_path = try comp.dirs.zig_lib.join(arena, &.{"libtsan"});
123 for (tsan_sources) |tsan_src| {
124 var cflags = std.array_list.Managed([]const u8).init(arena);
125
126 try cflags.append("-I");
127 try cflags.append(tsan_include_path);
128
129 try addCcArgs(target, &cflags);
130
131 c_source_files.appendAssumeCapacity(.{
132 .src_path = try comp.dirs.zig_lib.join(arena, &.{ "libtsan", tsan_src }),
133 .extra_flags = cflags.items,
134 .owner = root_mod,
135 });
136 }
137
138 const platform_tsan_sources = switch (target.os.tag) {
139 .driverkit, .ios, .maccatalyst, .macos, .watchos, .tvos, .visionos => &darwin_tsan_sources,
140 .windows => &windows_tsan_sources,
141 else => &unix_tsan_sources,
142 };
143 try c_source_files.ensureUnusedCapacity(platform_tsan_sources.len);
144 for (platform_tsan_sources) |tsan_src| {
145 var cflags = std.array_list.Managed([]const u8).init(arena);
146
147 try cflags.append("-I");
148 try cflags.append(tsan_include_path);
149
150 try addCcArgs(target, &cflags);
151
152 c_source_files.appendAssumeCapacity(.{
153 .src_path = try comp.dirs.zig_lib.join(arena, &.{ "libtsan", tsan_src }),
154 .extra_flags = cflags.items,
155 .owner = root_mod,
156 });
157 }
158 {
159 const asm_source = switch (target.cpu.arch) {
160 .aarch64, .aarch64_be => "tsan_rtl_aarch64.S",
161 .loongarch64 => "tsan_rtl_loongarch64.S",
162 .mips64, .mips64el => "tsan_rtl_mips64.S",
163 .powerpc64, .powerpc64le => "tsan_rtl_ppc64.S",
164 .riscv64 => "tsan_rtl_riscv64.S",
165 .s390x => "tsan_rtl_s390x.S",
166 .x86_64 => "tsan_rtl_amd64.S",
167 else => return error.TSANUnsupportedCPUArchitecture,
168 };
169 var cflags = std.array_list.Managed([]const u8).init(arena);
170
171 try cflags.append("-I");
172 try cflags.append(tsan_include_path);
173
174 try cflags.append("-DNDEBUG");
175
176 c_source_files.appendAssumeCapacity(.{
177 .src_path = try comp.dirs.zig_lib.join(arena, &.{ "libtsan", asm_source }),
178 .extra_flags = cflags.items,
179 .owner = root_mod,
180 });
181 }
182
183 try c_source_files.ensureUnusedCapacity(sanitizer_common_sources.len);
184 const sanitizer_common_include_path = try comp.dirs.zig_lib.join(arena, &.{
185 "libtsan", "sanitizer_common",
186 });
187 for (sanitizer_common_sources) |common_src| {
188 var cflags = std.array_list.Managed([]const u8).init(arena);
189
190 try cflags.append("-I");
191 try cflags.append(sanitizer_common_include_path);
192 try cflags.append("-I");
193 try cflags.append(tsan_include_path);
194
195 try addCcArgs(target, &cflags);
196
197 c_source_files.appendAssumeCapacity(.{
198 .src_path = try comp.dirs.zig_lib.join(arena, &.{
199 "libtsan", "sanitizer_common", common_src,
200 }),
201 .extra_flags = cflags.items,
202 .owner = root_mod,
203 });
204 }
205
206 const to_c_or_not_to_c_sources = if (comp.config.link_libc)
207 &sanitizer_libcdep_sources
208 else
209 &sanitizer_nolibc_sources;
210 try c_source_files.ensureUnusedCapacity(to_c_or_not_to_c_sources.len);
211 for (to_c_or_not_to_c_sources) |c_src| {
212 var cflags = std.array_list.Managed([]const u8).init(arena);
213
214 try cflags.append("-I");
215 try cflags.append(sanitizer_common_include_path);
216 try cflags.append("-I");
217 try cflags.append(tsan_include_path);
218
219 try addCcArgs(target, &cflags);
220
221 c_source_files.appendAssumeCapacity(.{
222 .src_path = try comp.dirs.zig_lib.join(arena, &.{
223 "libtsan", "sanitizer_common", c_src,
224 }),
225 .extra_flags = cflags.items,
226 .owner = root_mod,
227 });
228 }
229
230 try c_source_files.ensureUnusedCapacity(sanitizer_symbolizer_sources.len);
231 for (sanitizer_symbolizer_sources) |c_src| {
232 var cflags = std.array_list.Managed([]const u8).init(arena);
233
234 try cflags.append("-I");
235 try cflags.append(tsan_include_path);
236
237 try addCcArgs(target, &cflags);
238
239 c_source_files.appendAssumeCapacity(.{
240 .src_path = try comp.dirs.zig_lib.join(arena, &.{
241 "libtsan", "sanitizer_common", c_src,
242 }),
243 .extra_flags = cflags.items,
244 .owner = root_mod,
245 });
246 }
247
248 const interception_include_path = try comp.dirs.zig_lib.join(arena, &.{"interception"});
249
250 try c_source_files.ensureUnusedCapacity(interception_sources.len);
251 for (interception_sources) |c_src| {
252 var cflags = std.array_list.Managed([]const u8).init(arena);
253
254 try cflags.append("-I");
255 try cflags.append(interception_include_path);
256
257 try cflags.append("-I");
258 try cflags.append(tsan_include_path);
259
260 try addCcArgs(target, &cflags);
261
262 c_source_files.appendAssumeCapacity(.{
263 .src_path = try comp.dirs.zig_lib.join(arena, &.{
264 "libtsan", "interception", c_src,
265 }),
266 .extra_flags = cflags.items,
267 .owner = root_mod,
268 });
269 }
270
271 const skip_linker_dependencies = !target.os.tag.isDarwin();
272 const linker_allow_shlib_undefined = target.os.tag.isDarwin();
273 const install_name = if (target.os.tag.isDarwin())
274 try std.fmt.allocPrintSentinel(arena, "@rpath/{s}", .{basename}, 0)
275 else
276 null;
277 // Workaround for https://github.com/llvm/llvm-project/issues/97627
278 const headerpad_size: ?u32 = if (target.os.tag.isDarwin()) 32 else null;
279
280 const misc_task: Compilation.MiscTask = .libtsan;
281
282 var sub_create_diag: Compilation.CreateDiagnostic = undefined;
283 const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
284 .thread_limit = comp.thread_limit,
285 .dirs = comp.dirs.withoutLocalCache(),
286 .self_exe_path = comp.self_exe_path,
287 .cache_mode = .whole,
288 .config = config,
289 .root_mod = root_mod,
290 .root_name = root_name,
291 .libc_installation = comp.libc_installation,
292 .emit_bin = .yes_cache,
293 .function_sections = true,
294 .data_sections = true,
295 .c_source_files = c_source_files.items,
296 .verbose_cc = comp.verbose_cc,
297 .verbose_link = comp.verbose_link,
298 .verbose_air = comp.verbose_air,
299 .verbose_llvm_ir = comp.verbose_llvm_ir,
300 .verbose_llvm_bc = comp.verbose_llvm_bc,
301 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
302 .clang_passthrough_mode = comp.clang_passthrough_mode,
303 .skip_linker_dependencies = skip_linker_dependencies,
304 .linker_allow_shlib_undefined = linker_allow_shlib_undefined,
305 .install_name = install_name,
306 .headerpad_size = headerpad_size,
307 .environ_map = comp.environ_map,
308 }) catch |err| {
309 switch (err) {
310 else => comp.lockAndSetMiscFailure(misc_task, "unable to build {t}: create compilation failed: {t}", .{ misc_task, err }),
311 error.CreateFail => comp.lockAndSetMiscFailure(misc_task, "unable to build {t}: create compilation failed: {f}", .{ misc_task, sub_create_diag }),
312 }
313 return error.AlreadyReported;
314 };
315 defer sub_compilation.destroy();
316
317 comp.updateSubCompilation(sub_compilation, misc_task, prog_node) catch |err| switch (err) {
318 error.AlreadyReported => |e| return e,
319 else => |e| {
320 comp.lockAndSetMiscFailure(misc_task, "unable to build {t}: compilation failed: {s}", .{ misc_task, @errorName(e) });
321 return error.AlreadyReported;
322 },
323 };
324
325 // libtsan contains `.preinit_array` entries that must run for correctness, hence `must_link = true`.
326 const crt_file = try sub_compilation.toCrtFile();
327 try comp.queuePrelinkTaskMode(crt_file.full_object_path, true, &config);
328 assert(comp.tsan_lib == null);
329 comp.tsan_lib = crt_file;
330}
331
332fn addCcArgs(target: *const std.Target, args: *std.array_list.Managed([]const u8)) error{OutOfMemory}!void {
333 try args.appendSlice(&[_][]const u8{
334 "-nostdinc++",
335 "-fvisibility=hidden",
336 "-fvisibility-inlines-hidden",
337 "-std=c++17",
338 "-fno-rtti",
339 "-fno-exceptions",
340 "-w", // Disable all warnings.
341 });
342
343 if (target.abi.isAndroid() and target.os.version_range.linux.android >= 29) {
344 try args.append("-fno-emulated-tls");
345 }
346
347 if (target.isMinGW()) {
348 try args.append("-fms-extensions");
349 }
350}
351
352const tsan_sources = [_][]const u8{
353 "tsan_debugging.cpp",
354 "tsan_external.cpp",
355 "tsan_fd.cpp",
356 "tsan_flags.cpp",
357 "tsan_ignoreset.cpp",
358 "tsan_interceptors_memintrinsics.cpp",
359 "tsan_interceptors_posix.cpp",
360 "tsan_interface.cpp",
361 "tsan_interface_ann.cpp",
362 "tsan_interface_atomic.cpp",
363 "tsan_interface_java.cpp",
364 "tsan_malloc_mac.cpp",
365 "tsan_md5.cpp",
366 "tsan_mman.cpp",
367 "tsan_mutexset.cpp",
368 "tsan_new_delete.cpp",
369 "tsan_platform_windows.cpp",
370 "tsan_preinit.cpp",
371 "tsan_report.cpp",
372 "tsan_rtl.cpp",
373 "tsan_rtl_access.cpp",
374 "tsan_rtl_mutex.cpp",
375 "tsan_rtl_proc.cpp",
376 "tsan_rtl_report.cpp",
377 "tsan_rtl_thread.cpp",
378 "tsan_stack_trace.cpp",
379 "tsan_suppressions.cpp",
380 "tsan_symbolize.cpp",
381 "tsan_sync.cpp",
382 "tsan_vector_clock.cpp",
383};
384
385const darwin_tsan_sources = [_][]const u8{
386 "tsan_interceptors_mac.cpp",
387 "tsan_interceptors_mach_vm.cpp",
388 "tsan_platform_mac.cpp",
389 "tsan_platform_posix.cpp",
390};
391
392const unix_tsan_sources = [_][]const u8{
393 "tsan_platform_linux.cpp",
394 "tsan_platform_posix.cpp",
395};
396
397const windows_tsan_sources = [_][]const u8{
398 "tsan_platform_windows.cpp",
399};
400
401const sanitizer_common_sources = [_][]const u8{
402 "sanitizer_allocator.cpp",
403 "sanitizer_chained_origin_depot.cpp",
404 "sanitizer_common.cpp",
405 "sanitizer_deadlock_detector1.cpp",
406 "sanitizer_deadlock_detector2.cpp",
407 "sanitizer_errno.cpp",
408 "sanitizer_file.cpp",
409 "sanitizer_flag_parser.cpp",
410 "sanitizer_flags.cpp",
411 "sanitizer_fuchsia.cpp",
412 "sanitizer_haiku.cpp",
413 "sanitizer_libc.cpp",
414 "sanitizer_libignore.cpp",
415 "sanitizer_linux.cpp",
416 "sanitizer_linux_s390.cpp",
417 "sanitizer_mac.cpp",
418 "sanitizer_mutex.cpp",
419 "sanitizer_netbsd.cpp",
420 "sanitizer_platform_limits_freebsd.cpp",
421 "sanitizer_platform_limits_linux.cpp",
422 "sanitizer_platform_limits_netbsd.cpp",
423 "sanitizer_platform_limits_posix.cpp",
424 "sanitizer_platform_limits_solaris.cpp",
425 "sanitizer_posix.cpp",
426 "sanitizer_printf.cpp",
427 "sanitizer_procmaps_bsd.cpp",
428 "sanitizer_procmaps_common.cpp",
429 "sanitizer_procmaps_fuchsia.cpp",
430 "sanitizer_procmaps_haiku.cpp",
431 "sanitizer_procmaps_linux.cpp",
432 "sanitizer_procmaps_mac.cpp",
433 "sanitizer_procmaps_solaris.cpp",
434 "sanitizer_range.cpp",
435 "sanitizer_solaris.cpp",
436 "sanitizer_stoptheworld_fuchsia.cpp",
437 "sanitizer_stoptheworld_mac.cpp",
438 "sanitizer_stoptheworld_win.cpp",
439 "sanitizer_suppressions.cpp",
440 "sanitizer_termination.cpp",
441 "sanitizer_thread_arg_retval.cpp",
442 "sanitizer_thread_registry.cpp",
443 "sanitizer_tls_get_addr.cpp",
444 "sanitizer_type_traits.cpp",
445 "sanitizer_win.cpp",
446 "sanitizer_win_interception.cpp",
447};
448
449const sanitizer_nolibc_sources = [_][]const u8{
450 "sanitizer_common_nolibc.cpp",
451};
452
453const sanitizer_libcdep_sources = [_][]const u8{
454 "sanitizer_common_libcdep.cpp",
455 "sanitizer_allocator_checks.cpp",
456 "sanitizer_dl.cpp",
457 "sanitizer_linux_libcdep.cpp",
458 "sanitizer_mac_libcdep.cpp",
459 "sanitizer_posix_libcdep.cpp",
460 "sanitizer_stoptheworld_linux_libcdep.cpp",
461 "sanitizer_stoptheworld_netbsd_libcdep.cpp",
462};
463
464const sanitizer_symbolizer_sources = [_][]const u8{
465 "sanitizer_allocator_report.cpp",
466 "sanitizer_stack_store.cpp",
467 "sanitizer_stackdepot.cpp",
468 "sanitizer_stacktrace.cpp",
469 "sanitizer_stacktrace_libcdep.cpp",
470 "sanitizer_stacktrace_printer.cpp",
471 "sanitizer_stacktrace_sparc.cpp",
472 "sanitizer_symbolizer.cpp",
473 "sanitizer_symbolizer_libbacktrace.cpp",
474 "sanitizer_symbolizer_libcdep.cpp",
475 "sanitizer_symbolizer_mac.cpp",
476 "sanitizer_symbolizer_markup.cpp",
477 "sanitizer_symbolizer_markup_fuchsia.cpp",
478 "sanitizer_symbolizer_posix_libcdep.cpp",
479 "sanitizer_symbolizer_report.cpp",
480 "sanitizer_symbolizer_report_fuchsia.cpp",
481 "sanitizer_symbolizer_win.cpp",
482 "sanitizer_thread_history.cpp",
483 "sanitizer_unwind_linux_libcdep.cpp",
484 "sanitizer_unwind_fuchsia.cpp",
485 "sanitizer_unwind_win.cpp",
486};
487
488const interception_sources = [_][]const u8{
489 "interception_linux.cpp",
490 "interception_mac.cpp",
491 "interception_win.cpp",
492 "interception_type_test.cpp",
493};