diff --git a/src/Module.zig b/src/Module.zig index 7ea69a0a2e66..f3f1aa44e2cc 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3538,44 +3538,61 @@ pub fn astGenFile(mod: *Module, file: *File) !void { const cache_directory = if (want_local_cache) mod.local_zir_cache else mod.global_zir_cache; const zir_dir = cache_directory.handle; - var cache_file: ?std.fs.File = null; - defer if (cache_file) |f| f.close(); - // Determine whether we need to reload the file from disk and redo parsing and AstGen. - switch (file.status) { - .never_loaded, .retryable_failure => cached: { + var lock: std.fs.File.Lock = switch (file.status) { + .never_loaded, .retryable_failure => lock: { // First, load the cached ZIR code, if any. log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{ file.sub_file_path, want_local_cache, &digest, }); - // We ask for a lock in order to coordinate with other zig processes. - // If another process is already working on this file, we will get the cached - // version. Likewise if we're working on AstGen and another process asks for - // the cached file, they'll get it. - cache_file = zir_dir.openFile(&digest, .{ .lock = .Shared }) catch |err| switch (err) { - error.PathAlreadyExists => unreachable, // opening for reading - error.NoSpaceLeft => unreachable, // opening for reading - error.NotDir => unreachable, // no dir components - error.InvalidUtf8 => unreachable, // it's a hex encoded name - error.BadPathName => unreachable, // it's a hex encoded name - error.NameTooLong => unreachable, // it's a fixed size name - error.PipeBusy => unreachable, // it's not a pipe - error.WouldBlock => unreachable, // not asking for non-blocking I/O - - error.SymLinkLoop, - error.FileNotFound, - error.Unexpected, - => break :cached, - - else => |e| return e, // Retryable errors are handled at callsite. - }; + break :lock .Shared; + }, + .parse_failure, .astgen_failure, .success_zir => lock: { + const unchanged_metadata = + stat.size == file.stat.size and + stat.mtime == file.stat.mtime and + stat.inode == file.stat.inode; + + if (unchanged_metadata) { + log.debug("unmodified metadata of file: {s}", .{file.sub_file_path}); + return; + } + + log.debug("metadata changed: {s}", .{file.sub_file_path}); + + break :lock .Exclusive; + }, + }; + + // We ask for a lock in order to coordinate with other zig processes. + // If another process is already working on this file, we will get the cached + // version. Likewise if we're working on AstGen and another process asks for + // the cached file, they'll get it. + const cache_file = zir_dir.createFile(&digest, .{ + .read = true, + .truncate = false, + .lock = lock, + }) catch |err| switch (err) { + error.NotDir => unreachable, // no dir components + error.InvalidUtf8 => unreachable, // it's a hex encoded name + error.BadPathName => unreachable, // it's a hex encoded name + error.NameTooLong => unreachable, // it's a fixed size name + error.PipeBusy => unreachable, // it's not a pipe + error.WouldBlock => unreachable, // not asking for non-blocking I/O + error.FileNotFound => unreachable, // no dir components + + else => |e| return e, // Retryable errors are handled at callsite. + }; + defer cache_file.close(); + while (true) { + update: { // First we read the header to determine the lengths of arrays. - const header = cache_file.?.reader().readStruct(Zir.Header) catch |err| switch (err) { + const header = cache_file.reader().readStruct(Zir.Header) catch |err| switch (err) { // This can happen if Zig bails out of this function between creating // the cached file and writing it. - error.EndOfStream => break :cached, + error.EndOfStream => break :update, else => |e| return e, }; const unchanged_metadata = @@ -3585,7 +3602,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { if (!unchanged_metadata) { log.debug("AstGen cache stale: {s}", .{file.sub_file_path}); - break :cached; + break :update; } log.debug("AstGen cache hit: {s} instructions_len={d}", .{ file.sub_file_path, header.instructions_len, @@ -3637,13 +3654,13 @@ pub fn astGenFile(mod: *Module, file: *File) !void { .iov_len = header.extra_len * 4, }, }; - const amt_read = try cache_file.?.readvAll(&iovecs); + const amt_read = try cache_file.readvAll(&iovecs); const amt_expected = zir.instructions.len * 9 + zir.string_bytes.len + zir.extra.len * 4; if (amt_read != amt_expected) { log.warn("unexpected EOF reading cached ZIR for {s}", .{file.sub_file_path}); - break :cached; + break :update; } if (data_has_safety_tag) { const tags = zir.instructions.items(.tag); @@ -3679,42 +3696,22 @@ pub fn astGenFile(mod: *Module, file: *File) !void { return error.AnalysisFail; } return; - }, - .parse_failure, .astgen_failure, .success_zir => { - const unchanged_metadata = - stat.size == file.stat.size and - stat.mtime == file.stat.mtime and - stat.inode == file.stat.inode; - - if (unchanged_metadata) { - log.debug("unmodified metadata of file: {s}", .{file.sub_file_path}); - return; - } + } - log.debug("metadata changed: {s}", .{file.sub_file_path}); - }, - } - if (cache_file) |f| { - f.close(); - cache_file = null; + // If we already have the exclusive lock then it is our job to update. + if (builtin.os.tag == .wasi or lock == .Exclusive) break; + // Otherwise, unlock to give someone a chance to get the exclusive lock + // and then upgrade to an exclusive lock. + cache_file.unlock(); + lock = .Exclusive; + try cache_file.lock(lock); } - cache_file = zir_dir.createFile(&digest, .{ .lock = .Exclusive }) catch |err| switch (err) { - error.NotDir => unreachable, // no dir components - error.InvalidUtf8 => unreachable, // it's a hex encoded name - error.BadPathName => unreachable, // it's a hex encoded name - error.NameTooLong => unreachable, // it's a fixed size name - error.PipeBusy => unreachable, // it's not a pipe - error.WouldBlock => unreachable, // not asking for non-blocking I/O - error.FileNotFound => unreachable, // no dir components - else => |e| { - const pkg_path = file.pkg.root_src_directory.path orelse "."; - const cache_path = cache_directory.path orelse "."; - log.warn("unable to save cached ZIR code for {s}/{s} to {s}/{s}: {s}", .{ - pkg_path, file.sub_file_path, cache_path, &digest, @errorName(e), - }); - return; - }, + // The cache is definitely stale so delete the contents to avoid an underwrite later. + cache_file.setEndPos(0) catch |err| switch (err) { + error.FileTooBig => unreachable, // 0 is not too big + + else => |e| return e, }; mod.lockAndClearFileCompileError(file); @@ -3871,7 +3868,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { .iov_len = file.zir.extra.len * 4, }, }; - cache_file.?.writevAll(&iovecs) catch |err| { + cache_file.writevAll(&iovecs) catch |err| { const pkg_path = file.pkg.root_src_directory.path orelse "."; const cache_path = cache_directory.path orelse "."; log.warn("unable to write cached ZIR code for {s}/{s} to {s}/{s}: {s}", .{ diff --git a/stage1/wasi.c b/stage1/wasi.c index 911ce6e52053..6c4ac48a5003 100644 --- a/stage1/wasi.c +++ b/stage1/wasi.c @@ -497,8 +497,6 @@ uint32_t wasi_snapshot_preview1_fd_read(uint32_t fd, uint32_t iovs, uint32_t iov size_t read_size = 0; if (fds[fd].stream != NULL) read_size = fread(&m[iovs_ptr[i].ptr], 1, iovs_ptr[i].len, fds[fd].stream); - else - panic("unimplemented"); size += read_size; if (read_size < iovs_ptr[i].len) break; }