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