authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-15 16:55:56-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-20 15:08:59-07:00
log01d33855c736b04160d9616f138442aa4e41a738
tree16f5d96870a2a69de0786958576c07ce688b8a47
parent4964bb3282bf13de03a79fad1fb9bca104dc1930

stage2: protect mutable state from data races in updateCObject


1 files changed, 53 insertions(+), 28 deletions(-)

src/Compilation.zig+53-28
......@@ -41,7 +41,12 @@ link_error_flags: link.File.ErrorFlags = .{},
4141
4242work_queue: std.fifo.LinearFifo(Job, .Dynamic),
4343
44/// These jobs are to invoke the Clang compiler to create an object file, which
45/// gets linked with the Compilation.
46c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic),
47
4448/// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator.
49/// This data is accessed by multiple threads and is protected by `mutex`.
4550failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *ErrorMsg) = .{},
4651
4752keep_source_files_loaded: bool,
......@@ -111,6 +116,9 @@ owned_link_dir: ?std.fs.Dir,
111116/// Don't use this for anything other than stage1 compatibility.
112117color: @import("main.zig").Color = .auto,
113118
119/// This mutex guards all `Compilation` mutable state.
120mutex: std.Mutex = .{},
121
114122test_filter: ?[]const u8,
115123test_name_prefix: ?[]const u8,
116124test_evented_io: bool,
......@@ -150,9 +158,6 @@ const Job = union(enum) {
150158 /// The source file containing the Decl has been updated, and so the
151159 /// Decl may need its line number information updated in the debug info.
152160 update_line_number: *Module.Decl,
153 /// Invoke the Clang compiler to create an object file, which gets linked
154 /// with the Compilation.
155 c_object: *CObject,
156161
157162 /// one of the glibc static objects
158163 glibc_crt_file: glibc.CRTFile,
......@@ -971,6 +976,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
971976 .emit_analysis = options.emit_analysis,
972977 .emit_docs = options.emit_docs,
973978 .work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa),
979 .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa),
974980 .keep_source_files_loaded = options.keep_source_files_loaded,
975981 .use_clang = use_clang,
976982 .clang_argv = options.clang_argv,
......@@ -1190,11 +1196,13 @@ pub fn update(self: *Compilation) !void {
11901196 const tracy = trace(@src());
11911197 defer tracy.end();
11921198
1199 self.c_object_cache_digest_set.clearRetainingCapacity();
1200
11931201 // For compiling C objects, we rely on the cache hash system to avoid duplicating work.
11941202 // Add a Job for each C object.
1195 try self.work_queue.ensureUnusedCapacity(self.c_object_table.items().len);
1203 try self.c_object_work_queue.ensureUnusedCapacity(self.c_object_table.items().len);
11961204 for (self.c_object_table.items()) |entry| {
1197 self.work_queue.writeItemAssumeCapacity(.{ .c_object = entry.key });
1205 self.c_object_work_queue.writeItemAssumeCapacity(entry.key);
11981206 }
11991207
12001208 const use_stage1 = build_options.is_stage1 and self.bin_file.options.use_llvm;
......@@ -1372,6 +1380,26 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
13721380 var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len);
13731381 defer c_comp_progress_node.end();
13741382
1383 while (self.c_object_work_queue.readItem()) |c_object| {
1384 self.updateCObject(c_object, &c_comp_progress_node) catch |err| switch (err) {
1385 error.AnalysisFail => continue,
1386 else => {
1387 {
1388 var lock = self.mutex.acquire();
1389 defer lock.release();
1390 try self.failed_c_objects.ensureCapacity(self.gpa, self.failed_c_objects.items().len + 1);
1391 self.failed_c_objects.putAssumeCapacityNoClobber(c_object, try ErrorMsg.create(
1392 self.gpa,
1393 0,
1394 "unable to build C object: {s}",
1395 .{@errorName(err)},
1396 ));
1397 }
1398 c_object.status = .{ .failure = {} };
1399 },
1400 };
1401 }
1402
13751403 while (self.work_queue.readItem()) |work_item| switch (work_item) {
13761404 .codegen_decl => |decl| switch (decl.analysis) {
13771405 .unreferenced => unreachable,
......@@ -1447,21 +1475,6 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
14471475 decl.analysis = .codegen_failure_retryable;
14481476 };
14491477 },
1450 .c_object => |c_object| {
1451 self.updateCObject(c_object, &c_comp_progress_node) catch |err| switch (err) {
1452 error.AnalysisFail => continue,
1453 else => {
1454 try self.failed_c_objects.ensureCapacity(self.gpa, self.failed_c_objects.items().len + 1);
1455 self.failed_c_objects.putAssumeCapacityNoClobber(c_object, try ErrorMsg.create(
1456 self.gpa,
1457 0,
1458 "unable to build C object: {s}",
1459 .{@errorName(err)},
1460 ));
1461 c_object.status = .{ .failure = {} };
1462 },
1463 };
1464 },
14651478 .glibc_crt_file => |crt_file| {
14661479 glibc.buildCRTFile(self, crt_file) catch |err| {
14671480 // TODO Expose this as a normal compile error rather than crashing here.
......@@ -1553,7 +1566,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
15531566 };
15541567}
15551568
1556pub fn obtainCObjectCacheManifest(comp: *Compilation) Cache.Manifest {
1569pub fn obtainCObjectCacheManifest(comp: *const Compilation) Cache.Manifest {
15571570 var man = comp.cache_parent.obtain();
15581571
15591572 // Only things that need to be added on top of the base hash, and only things
......@@ -1720,6 +1733,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *
17201733
17211734 if (c_object.clearStatus(comp.gpa)) {
17221735 // There was previous failure.
1736 var lock = comp.mutex.acquire();
1737 defer lock.release();
17231738 comp.failed_c_objects.removeAssertDiscard(c_object);
17241739 }
17251740
......@@ -1747,8 +1762,14 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *
17471762 }
17481763
17491764 {
1750 const gop = try comp.c_object_cache_digest_set.getOrPut(comp.gpa, man.hash.peekBin());
1751 if (gop.found_existing) {
1765 const is_collision = blk: {
1766 var lock = comp.mutex.acquire();
1767 defer lock.release();
1768
1769 const gop = try comp.c_object_cache_digest_set.getOrPut(comp.gpa, man.hash.peekBin());
1770 break :blk gop.found_existing;
1771 };
1772 if (is_collision) {
17521773 return comp.failCObj(
17531774 c_object,
17541775 "the same source file was already added to the same compilation with the same flags",
......@@ -1929,7 +1950,7 @@ pub fn addTranslateCCArgs(
19291950
19301951/// Add common C compiler args between translate-c and C object compilation.
19311952pub fn addCCArgs(
1932 comp: *Compilation,
1953 comp: *const Compilation,
19331954 arena: *Allocator,
19341955 argv: *std.ArrayList([]const u8),
19351956 ext: FileExt,
......@@ -2164,10 +2185,14 @@ fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8,
21642185
21652186fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *ErrorMsg) InnerError {
21662187 {
2167 errdefer err_msg.destroy(comp.gpa);
2168 try comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1);
2188 var lock = comp.mutex.acquire();
2189 defer lock.release();
2190 {
2191 errdefer err_msg.destroy(comp.gpa);
2192 try comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1);
2193 }
2194 comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, err_msg);
21692195 }
2170 comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, err_msg);
21712196 c_object.status = .failure;
21722197 return error.AnalysisFail;
21732198}
......@@ -2324,7 +2349,7 @@ test "classifyFileExt" {
23242349 std.testing.expectEqual(FileExt.zir, classifyFileExt("foo.zir"));
23252350}
23262351
2327fn haveFramePointer(comp: *Compilation) bool {
2352fn haveFramePointer(comp: *const Compilation) bool {
23282353 // If you complicate this logic make sure you update the parent cache hash.
23292354 // Right now it's not in the cache hash because the value depends on optimize_mode
23302355 // and strip which are both already part of the hash.