diff --git a/doc/langref.html.in b/doc/langref.html.in index 8c118cf3b2..b742119af7 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7455,6 +7455,10 @@ fn add(a: i32, b: i32) i32 { return a + b; } Attempting to convert a number which is out of range of the destination type results in safety-protected {#link|Undefined Behavior#}.

+

+ If {#syntax#}T{#endsyntax#} is {#syntax#}comptime_int{#endsyntax#}, + then this is semantically equivalent to {#link|Type Coercion#}. +

{#header_close#} {#header_open|@intToEnum#} @@ -8206,10 +8210,6 @@ test "integer truncation" { This function always truncates the significant bits of the integer, regardless of endianness on the target platform.

-

- If {#syntax#}T{#endsyntax#} is {#syntax#}comptime_int{#endsyntax#}, - then this is semantically equivalent to {#link|Type Coercion#}. -

{#header_close#} {#header_open|@Type#} diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 6edc472c20..c718972537 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -40,6 +40,14 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { .allocator = allocator, }; } + + /// Initialize with capacity to hold at least num elements. + /// Deinitialize with `deinit` or use `toOwnedSlice`. + pub fn initCapacity(allocator: *Allocator, num: usize) !Self { + var self = Self.init(allocator); + try self.ensureCapacity(num); + return self; + } /// Release all allocated memory. pub fn deinit(self: Self) void { @@ -271,6 +279,15 @@ test "std.ArrayList.init" { testing.expect(list.capacity() == 0); } +test "std.ArrayList.initCapacity" { + var bytes: [1024]u8 = undefined; + const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator; + var list = try ArrayList(i8).initCapacity(allocator, 200); + defer list.deinit(); + testing.expect(list.count() == 0); + testing.expect(list.capacity() >= 200); +} + test "std.ArrayList.basic" { var bytes: [1024]u8 = undefined; const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator; diff --git a/lib/std/buffer.zig b/lib/std/buffer.zig index 37414d64d2..46c55a32b8 100644 --- a/lib/std/buffer.zig +++ b/lib/std/buffer.zig @@ -16,13 +16,22 @@ pub const Buffer = struct { mem.copy(u8, self.list.items, m); return self; } - + + /// Initialize memory to size bytes of undefined values. /// Must deinitialize with deinit. pub fn initSize(allocator: *Allocator, size: usize) !Buffer { var self = initNull(allocator); try self.resize(size); return self; } + + /// Initialize with capacity to hold at least num bytes. + /// Must deinitialize with deinit. + pub fn initCapacity(allocator: *Allocator, num: usize) !Buffer { + var self = Buffer{ .list = try ArrayList(u8).initCapacity(allocator, num + 1) }; + self.list.appendAssumeCapacity(0); + return self; + } /// Must deinitialize with deinit. /// None of the other operations are valid until you do one of these: @@ -98,6 +107,13 @@ pub const Buffer = struct { pub fn len(self: Buffer) usize { return self.list.len - 1; } + + pub fn capacity(self: Buffer) usize { + return if (self.list.items.len > 0) + self.list.items.len - 1 + else + 0; + } pub fn append(self: *Buffer, m: []const u8) !void { const old_len = self.len(); @@ -151,3 +167,21 @@ test "simple Buffer" { try buf2.resize(4); testing.expect(buf.startsWith(buf2.toSlice())); } + +test "Buffer.initSize" { + var buf = try Buffer.initSize(debug.global_allocator, 3); + testing.expect(buf.len() == 3); + try buf.append("hello"); + testing.expect(mem.eql(u8, buf.toSliceConst()[3..], "hello")); +} + +test "Buffer.initCapacity" { + var buf = try Buffer.initCapacity(debug.global_allocator, 10); + testing.expect(buf.len() == 0); + testing.expect(buf.capacity() >= 10); + const old_cap = buf.capacity(); + try buf.append("hello"); + testing.expect(buf.len() == 5); + testing.expect(buf.capacity() == old_cap); + testing.expect(mem.eql(u8, buf.toSliceConst(), "hello")); +} diff --git a/lib/std/build.zig b/lib/std/build.zig index fe980aaf89..bfde2f52d7 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -2062,14 +2062,28 @@ pub const RunStep = struct { } pub fn addPathDir(self: *RunStep, search_path: []const u8) void { - const PATH = if (builtin.os == .windows) "Path" else "PATH"; const env_map = self.getEnvMap(); - const prev_path = env_map.get(PATH) orelse { - env_map.set(PATH, search_path) catch unreachable; - return; - }; - const new_path = self.builder.fmt("{}" ++ &[1]u8{fs.path.delimiter} ++ "{}", prev_path, search_path); - env_map.set(PATH, new_path) catch unreachable; + + var key: []const u8 = undefined; + var prev_path: ?[]const u8 = undefined; + if (builtin.os == .windows) { + key = "Path"; + prev_path = env_map.get(key); + if (prev_path == null) { + key = "PATH"; + prev_path = env_map.get(key); + } + } else { + key = "PATH"; + prev_path = env_map.get(key); + } + + if (prev_path) |pp| { + const new_path = self.builder.fmt("{}" ++ [1]u8{fs.path.delimiter} ++ "{}", pp, search_path); + env_map.set(key, new_path) catch unreachable; + } else { + env_map.set(key, search_path) catch unreachable; + } } pub fn getEnvMap(self: *RunStep) *BufMap { @@ -2178,7 +2192,7 @@ const InstallArtifactStep = struct { const full_dest_path = builder.getInstallPath(self.dest_dir, self.artifact.out_filename); try builder.updateFile(self.artifact.getOutputPath(), full_dest_path); - if (self.artifact.isDynamicLibrary()) { + if (self.artifact.isDynamicLibrary() and self.artifact.target.wantSharedLibSymLinks()) { try doAtomicSymLinks(builder.allocator, full_dest_path, self.artifact.major_only_filename, self.artifact.name_only_filename); } if (self.pdb_dir) |pdb_dir| { @@ -2405,7 +2419,7 @@ fn findVcpkgRoot(allocator: *Allocator) !?[]const u8 { const path_file = try fs.path.join(allocator, &[_][]const u8{ appdata_path, "vcpkg.path.txt" }); defer allocator.free(path_file); - const file = fs.File.openRead(path_file) catch return null; + const file = fs.cwd().openFile(path_file, .{}) catch return null; defer file.close(); const size = @intCast(usize, try file.getEndPos()); diff --git a/lib/std/c.zig b/lib/std/c.zig index 19c3c8feb8..9e70ff988d 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -220,6 +220,7 @@ pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) c_int; pub const PTHREAD_COND_INITIALIZER = pthread_cond_t{}; pub extern "c" fn pthread_cond_wait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t) c_int; +pub extern "c" fn pthread_cond_timedwait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t, noalias abstime: *const timespec) c_int; pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) c_int; pub extern "c" fn pthread_cond_destroy(cond: *pthread_cond_t) c_int; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index ecd3ebaef1..711f728fa6 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1131,7 +1131,7 @@ fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo { } fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void { - var f = try File.openRead(line_info.file_name); + var f = try fs.cwd().openFile(line_info.file_name, .{}); defer f.close(); // TODO fstat and make sure that the file has the correct size @@ -2089,7 +2089,7 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u const ofile_path = mem.toSliceConst(u8, @ptrCast([*:0]const u8, di.strings.ptr + ofile.n_strx)); gop.kv.value = MachOFile{ - .bytes = try std.fs.Dir.cwd().readFileAllocAligned( + .bytes = try std.fs.cwd().readFileAllocAligned( di.ofiles.allocator, ofile_path, maxInt(usize), @@ -2417,6 +2417,12 @@ extern fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: *con std.debug.warn("Segmentation fault at address 0x{x}\n", addr); switch (builtin.arch) { + .i386 => { + const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); + const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_EIP]); + const bp = @intCast(usize, ctx.mcontext.gregs[os.REG_EBP]); + dumpStackTraceFromBase(bp, ip); + }, .x86_64 => { const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_RIP]); diff --git a/lib/std/event/channel.zig b/lib/std/event/channel.zig index ac5a65e1b0..96cfd706be 100644 --- a/lib/std/event/channel.zig +++ b/lib/std/event/channel.zig @@ -54,6 +54,10 @@ pub fn Channel(comptime T: type) type { /// For a zero length buffer, use `[0]T{}`. /// TODO https://github.com/ziglang/zig/issues/2765 pub fn init(self: *SelfChannel, buffer: []T) void { + // The ring buffer implementation only works with power of 2 buffer sizes + // because of relying on subtracting across zero. For example (0 -% 1) % 10 == 5 + assert(buffer.len == 0 or @popCount(usize, buffer.len) == 1); + self.* = SelfChannel{ .buffer_len = 0, .buffer_nodes = buffer, @@ -184,11 +188,11 @@ pub fn Channel(comptime T: type) type { const get_node = &self.getters.get().?.data; switch (get_node.data) { GetNode.Data.Normal => |info| { - info.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len]; + info.ptr.* = self.buffer_nodes[(self.buffer_index -% self.buffer_len) % self.buffer_nodes.len]; }, GetNode.Data.OrNull => |info| { _ = self.or_null_queue.remove(info.or_null); - info.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len]; + info.ptr.* = self.buffer_nodes[(self.buffer_index -% self.buffer_len) % self.buffer_nodes.len]; }, } global_event_loop.onNextTick(get_node.tick_node); @@ -222,7 +226,7 @@ pub fn Channel(comptime T: type) type { while (self.buffer_len != self.buffer_nodes.len and put_count != 0) { const put_node = &self.putters.get().?.data; - self.buffer_nodes[self.buffer_index] = put_node.data; + self.buffer_nodes[self.buffer_index % self.buffer_nodes.len] = put_node.data; global_event_loop.onNextTick(put_node.tick_node); self.buffer_index +%= 1; self.buffer_len += 1; @@ -283,6 +287,29 @@ test "std.event.Channel" { await putter; } +test "std.event.Channel wraparound" { + + // TODO provide a way to run tests in evented I/O mode + if (!std.io.is_async) return error.SkipZigTest; + + const channel_size = 2; + + var buf : [channel_size]i32 = undefined; + var channel: Channel(i32) = undefined; + channel.init(&buf); + defer channel.deinit(); + + // add items to channel and pull them out until + // the buffer wraps around, make sure it doesn't crash. + var result : i32 = undefined; + channel.put(5); + testing.expectEqual(@as(i32, 5), channel.get()); + channel.put(6); + testing.expectEqual(@as(i32, 6), channel.get()); + channel.put(7); + testing.expectEqual(@as(i32, 7), channel.get()); +} + async fn testChannelGetter(channel: *Channel(i32)) void { const value1 = channel.get(); testing.expect(value1 == 1234); diff --git a/lib/std/event/fs.zig b/lib/std/event/fs.zig index 0bbc710dfc..346d0f294a 100644 --- a/lib/std/event/fs.zig +++ b/lib/std/event/fs.zig @@ -735,24 +735,26 @@ pub fn Watch(comptime V: type) type { allocator: *Allocator, const OsData = switch (builtin.os) { - .macosx, .freebsd, .netbsd, .dragonfly => struct { - file_table: FileTable, - table_lock: event.Lock, - - const FileTable = std.StringHashMap(*Put); - const Put = struct { - putter_frame: @Frame(kqPutEvents), - cancelled: bool = false, - value: V, - }; - }, - + // TODO https://github.com/ziglang/zig/issues/3778 + .macosx, .freebsd, .netbsd, .dragonfly => KqOsData, .linux => LinuxOsData, .windows => WindowsOsData, else => @compileError("Unsupported OS"), }; + const KqOsData = struct { + file_table: FileTable, + table_lock: event.Lock, + + const FileTable = std.StringHashMap(*Put); + const Put = struct { + putter_frame: @Frame(kqPutEvents), + cancelled: bool = false, + value: V, + }; + }; + const WindowsOsData = struct { table_lock: event.Lock, dir_table: DirTable, @@ -1291,7 +1293,7 @@ pub fn Watch(comptime V: type) type { os.linux.EINVAL => unreachable, os.linux.EFAULT => unreachable, os.linux.EAGAIN => { - global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN); + global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT); }, else => unreachable, } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 60ecff1fa3..9e7daf0a9a 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -13,8 +13,6 @@ pub const File = @import("fs/file.zig").File; pub const symLink = os.symlink; pub const symLinkC = os.symlinkC; -pub const deleteFile = os.unlink; -pub const deleteFileC = os.unlinkC; pub const rename = os.rename; pub const renameC = os.renameC; pub const renameW = os.renameW; @@ -88,13 +86,15 @@ pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus { /// If any of the directories do not exist for dest_path, they are created. /// TODO https://github.com/ziglang/zig/issues/2885 pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !PrevStatus { - var src_file = try File.openRead(source_path); + const my_cwd = cwd(); + + var src_file = try my_cwd.openFile(source_path, .{}); defer src_file.close(); const src_stat = try src_file.stat(); check_dest_stat: { const dest_stat = blk: { - var dest_file = File.openRead(dest_path) catch |err| switch (err) { + var dest_file = my_cwd.openFile(dest_path, .{}) catch |err| switch (err) { error.FileNotFound => break :check_dest_stat, else => |e| return e, }; @@ -157,7 +157,7 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil /// in the same directory as dest_path. /// Destination file will have the same mode as the source file. pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { - var in_file = try File.openRead(source_path); + var in_file = try cwd().openFile(source_path, .{}); defer in_file.close(); const mode = try in_file.mode(); @@ -180,7 +180,7 @@ pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { /// merged and readily available, /// there is a possibility of power loss or application termination leaving temporary files present pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { - var in_file = try File.openRead(source_path); + var in_file = try cwd().openFile(source_path, .{}); defer in_file.close(); var atomic_file = try AtomicFile.init(dest_path, mode); @@ -206,8 +206,6 @@ pub const AtomicFile = struct { /// dest_path must remain valid for the lifetime of AtomicFile /// call finish to atomically replace dest_path with contents - /// TODO once we have null terminated pointers, use the - /// openWriteNoClobberN function pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { const dirname = path.dirname(dest_path); var rand_buf: [12]u8 = undefined; @@ -224,15 +222,19 @@ pub const AtomicFile = struct { tmp_path_buf[tmp_path_len] = 0; + const my_cwd = cwd(); + while (true) { try crypto.randomBytes(rand_buf[0..]); b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], &rand_buf); - const file = File.openWriteNoClobberC(@ptrCast([*:0]u8, &tmp_path_buf), mode) catch |err| switch (err) { + // TODO https://github.com/ziglang/zig/issues/3770 to clean up this @ptrCast + const file = my_cwd.createFileC( + @ptrCast([*:0]u8, &tmp_path_buf), + .{ .mode = mode, .exclusive = true }, + ) catch |err| switch (err) { error.PathAlreadyExists => continue, - // TODO zig should figure out that this error set does not include PathAlreadyExists since - // it is handled in the above switch - else => return err, + else => |e| return e, }; return AtomicFile{ @@ -248,7 +250,7 @@ pub const AtomicFile = struct { pub fn deinit(self: *AtomicFile) void { if (!self.finished) { self.file.close(); - deleteFileC(@ptrCast([*:0]u8, &self.tmp_path_buf)) catch {}; + cwd().deleteFileC(@ptrCast([*:0]u8, &self.tmp_path_buf)) catch {}; self.finished = true; } } @@ -350,12 +352,12 @@ pub fn deleteTree(full_path: []const u8) !void { CannotDeleteRootDirectory, }.CannotDeleteRootDirectory; - var dir = try Dir.cwd().openDirList(dirname); + var dir = try cwd().openDirList(dirname); defer dir.close(); return dir.deleteTree(path.basename(full_path)); } else { - return Dir.cwd().deleteTree(full_path); + return cwd().deleteTree(full_path); } } @@ -657,17 +659,6 @@ pub const Dir = struct { } } - /// Returns an handle to the current working directory that is open for traversal. - /// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior. - /// On POSIX targets, this function is comptime-callable. - pub fn cwd() Dir { - if (builtin.os == .windows) { - return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; - } else { - return Dir{ .fd = os.AT_FDCWD }; - } - } - pub const OpenError = error{ FileNotFound, NotDir, @@ -683,12 +674,12 @@ pub const Dir = struct { DeviceBusy, } || os.UnexpectedError; - /// Deprecated; call `Dir.cwd().openDirList` directly. + /// Deprecated; call `cwd().openDirList` directly. pub fn open(dir_path: []const u8) OpenError!Dir { return cwd().openDirList(dir_path); } - /// Deprecated; call `Dir.cwd().openDirListC` directly. + /// Deprecated; call `cwd().openDirListC` directly. pub fn openC(dir_path_c: [*:0]const u8) OpenError!Dir { return cwd().openDirListC(dir_path_c); } @@ -698,29 +689,110 @@ pub const Dir = struct { self.* = undefined; } - /// Call `File.close` on the result when done. - pub fn openRead(self: Dir, sub_path: []const u8) File.OpenError!File { + /// Opens a file for reading or writing, without attempting to create a new file. + /// Call `File.close` to release the resource. + /// Asserts that the path parameter has no null bytes. + pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); - return self.openReadW(&path_w); + return self.openFileW(&path_w, flags); } const path_c = try os.toPosixPath(sub_path); - return self.openReadC(&path_c); + return self.openFileC(&path_c, flags); } - /// Call `File.close` on the result when done. - pub fn openReadC(self: Dir, sub_path: [*:0]const u8) File.OpenError!File { + /// Same as `openFile` but the path parameter is null-terminated. + pub fn openFileC(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File { if (builtin.os == .windows) { const path_w = try os.windows.cStrToPrefixedFileW(sub_path); - return self.openReadW(&path_w); + return self.openFileW(&path_w, flags); } const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; - const flags = O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC; - const fd = try os.openatC(self.fd, sub_path, flags, 0); - return File.openHandle(fd); + const os_flags = O_LARGEFILE | os.O_CLOEXEC | if (flags.write and flags.read) + @as(u32, os.O_RDWR) + else if (flags.write) + @as(u32, os.O_WRONLY) + else + @as(u32, os.O_RDONLY); + const fd = try os.openatC(self.fd, sub_path, os_flags, 0); + return File{ .handle = fd }; } - pub fn openReadW(self: Dir, sub_path_w: [*:0]const u16) File.OpenError!File { + /// Same as `openFile` but Windows-only and the path parameter is + /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. + pub fn openFileW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) File.OpenError!File { + const w = os.windows; + const access_mask = w.SYNCHRONIZE | + (if (flags.read) @as(u32, w.GENERIC_READ) else 0) | + (if (flags.write) @as(u32, w.GENERIC_WRITE) else 0); + return self.openFileWindows(sub_path_w, access_mask, w.FILE_OPEN); + } + + /// Creates, opens, or overwrites a file with write access. + /// Call `File.close` on the result when done. + /// Asserts that the path parameter has no null bytes. + pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); + if (builtin.os == .windows) { + const path_w = try os.windows.sliceToPrefixedFileW(sub_path); + return self.createFileW(&path_w, flags); + } + const path_c = try os.toPosixPath(sub_path); + return self.createFileC(&path_c, flags); + } + + /// Same as `createFile` but the path parameter is null-terminated. + pub fn createFileC(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File { + if (builtin.os == .windows) { + const path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); + return self.createFileW(&path_w, flags); + } + const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; + const os_flags = O_LARGEFILE | os.O_CREAT | os.O_CLOEXEC | + (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) | + (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) | + (if (flags.exclusive) @as(u32, os.O_EXCL) else 0); + const fd = try os.openatC(self.fd, sub_path_c, os_flags, flags.mode); + return File{ .handle = fd }; + } + + /// Same as `createFile` but Windows-only and the path parameter is + /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. + pub fn createFileW(self: Dir, sub_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File { + const w = os.windows; + const access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | + (if (flags.read) @as(u32, w.GENERIC_READ) else 0); + const creation = if (flags.exclusive) + @as(u32, w.FILE_CREATE) + else if (flags.truncate) + @as(u32, w.FILE_OVERWRITE_IF) + else + @as(u32, w.FILE_OPEN_IF); + return self.openFileWindows(sub_path_w, access_mask, creation); + } + + /// Deprecated; call `openFile` directly. + pub fn openRead(self: Dir, sub_path: []const u8) File.OpenError!File { + return self.openFile(sub_path, .{}); + } + + /// Deprecated; call `openFileC` directly. + pub fn openReadC(self: Dir, sub_path: [*:0]const u8) File.OpenError!File { + return self.openFileC(sub_path, .{}); + } + + /// Deprecated; call `openFileW` directly. + pub fn openReadW(self: Dir, sub_path: [*:0]const u16) File.OpenError!File { + return self.openFileW(sub_path, .{}); + } + + pub fn openFileWindows( + self: Dir, + sub_path_w: [*:0]const u16, + access_mask: os.windows.ACCESS_MASK, + creation: os.windows.ULONG, + ) File.OpenError!File { const w = os.windows; var result = File{ .handle = undefined }; @@ -750,13 +822,13 @@ pub const Dir = struct { var io: w.IO_STATUS_BLOCK = undefined; const rc = w.ntdll.NtCreateFile( &result.handle, - w.GENERIC_READ | w.SYNCHRONIZE, + access_mask, &attr, &io, null, w.FILE_ATTRIBUTE_NORMAL, - w.FILE_SHARE_READ, - w.FILE_OPEN, + w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE, + creation, w.FILE_NON_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT, null, 0, @@ -771,6 +843,7 @@ pub const Dir = struct { w.STATUS.ACCESS_DENIED => return error.AccessDenied, w.STATUS.PIPE_BUSY => return error.PipeBusy, w.STATUS.OBJECT_PATH_SYNTAX_BAD => unreachable, + w.STATUS.OBJECT_NAME_COLLISION => return error.PathAlreadyExists, else => return w.unexpectedStatus(rc), } } @@ -790,7 +863,10 @@ pub const Dir = struct { /// list the contents of a directory, open it with `openDirList`. /// /// Call `close` on the result when done. + /// + /// Asserts that the path parameter has no null bytes. pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openDirTraverseW(&sub_path_w); @@ -805,7 +881,10 @@ pub const Dir = struct { /// same and may be more efficient. /// /// Call `close` on the result when done. + /// + /// Asserts that the path parameter has no null bytes. pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openDirListW(&sub_path_w); @@ -920,9 +999,12 @@ pub const Dir = struct { pub const DeleteFileError = os.UnlinkError; /// Delete a file name and possibly the file it refers to, based on an open directory handle. + /// Asserts that the path parameter has no null bytes. pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { - const sub_path_c = try os.toPosixPath(sub_path); - return self.deleteFileC(&sub_path_c); + os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) { + error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR + else => |e| return e, + }; } /// Same as `deleteFile` except the parameter is null-terminated. @@ -933,6 +1015,14 @@ pub const Dir = struct { }; } + /// Same as `deleteFile` except the parameter is WTF-16 encoded. + pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void { + os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) { + error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR + else => |e| return e, + }; + } + pub const DeleteDirError = error{ DirNotEmpty, FileNotFound, @@ -951,7 +1041,9 @@ pub const Dir = struct { /// Returns `error.DirNotEmpty` if the directory is not empty. /// To delete a directory recursively, see `deleteTree`. + /// Asserts that the path parameter has no null bytes. pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.deleteDirW(&sub_path_w); @@ -979,7 +1071,9 @@ pub const Dir = struct { /// Read value of a symbolic link. /// The return value is a slice of `buffer`, from index `0`. + /// Asserts that the path parameter has no null bytes. pub fn readLink(self: Dir, sub_path: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); const sub_path_c = try os.toPosixPath(sub_path); return self.readLinkC(&sub_path_c, buffer); } @@ -1190,8 +1284,94 @@ pub const Dir = struct { } } } + + /// Writes content to the file system, creating a new file if it does not exist, truncating + /// if it already exists. + pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) !void { + var file = try self.createFile(sub_path, .{}); + defer file.close(); + try file.write(data); + } }; +/// Returns an handle to the current working directory that is open for traversal. +/// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior. +/// On POSIX targets, this function is comptime-callable. +pub fn cwd() Dir { + if (builtin.os == .windows) { + return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; + } else { + return Dir{ .fd = os.AT_FDCWD }; + } +} + +/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path. +/// Call `File.close` to release the resource. +/// Asserts that the path is absolute. See `Dir.openFile` for a function that +/// operates on both absolute and relative paths. +/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteC` for a function +/// that accepts a null-terminated path. +pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File { + assert(path.isAbsolute(absolute_path)); + return cwd().openFile(absolute_path, flags); +} + +/// Same as `openFileAbsolute` but the path parameter is null-terminated. +pub fn openFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File { + assert(path.isAbsoluteC(absolute_path_c)); + return cwd().openFileC(absolute_path_c, flags); +} + +/// Same as `openFileAbsolute` but the path parameter is WTF-16 encoded. +pub fn openFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.OpenFlags) File.OpenError!File { + assert(path.isAbsoluteW(absolute_path_w)); + return cwd().openFileW(absolute_path_w, flags); +} + +/// Creates, opens, or overwrites a file with write access, based on an absolute path. +/// Call `File.close` to release the resource. +/// Asserts that the path is absolute. See `Dir.createFile` for a function that +/// operates on both absolute and relative paths. +/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function +/// that accepts a null-terminated path. +pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File { + assert(path.isAbsolute(absolute_path)); + return cwd().createFile(absolute_path, flags); +} + +/// Same as `createFileAbsolute` but the path parameter is null-terminated. +pub fn createFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File { + assert(path.isAbsoluteC(absolute_path_c)); + return cwd().createFileC(absolute_path_c, flags); +} + +/// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded. +pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File { + assert(path.isAbsoluteW(absolute_path_w)); + return cwd().createFileW(absolute_path_w, flags); +} + +/// Delete a file name and possibly the file it refers to, based on an absolute path. +/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that +/// operates on both absolute and relative paths. +/// Asserts that the path parameter has no null bytes. +pub fn deleteFileAbsolute(absolute_path: []const u8) DeleteFileError!void { + assert(path.isAbsolute(absolute_path)); + return cwd().deleteFile(absolute_path); +} + +/// Same as `deleteFileAbsolute` except the parameter is null-terminated. +pub fn deleteFileAbsoluteC(absolute_path_c: [*:0]const u8) DeleteFileError!void { + assert(path.isAbsoluteC(absolute_path_c)); + return cwd().deleteFileC(absolute_path_c); +} + +/// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded. +pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) DeleteFileError!void { + assert(path.isAbsoluteW(absolute_path_w)); + return cwd().deleteFileW(absolute_path_w); +} + pub const Walker = struct { stack: std.ArrayList(StackItem), name_buffer: std.Buffer, @@ -1264,7 +1444,7 @@ pub const Walker = struct { pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { assert(!mem.endsWith(u8, dir_path, path.sep_str)); - var dir = try Dir.cwd().openDirList(dir_path); + var dir = try cwd().openDirList(dir_path); errdefer dir.close(); var name_buffer = try std.Buffer.init(allocator, dir_path); @@ -1298,18 +1478,18 @@ pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfE pub fn openSelfExe() OpenSelfExeError!File { if (builtin.os == .linux) { - return File.openReadC("/proc/self/exe"); + return openFileAbsoluteC("/proc/self/exe", .{}); } if (builtin.os == .windows) { const wide_slice = selfExePathW(); const prefixed_path_w = try os.windows.wToPrefixedFileW(wide_slice); - return Dir.cwd().openReadW(&prefixed_path_w); + return cwd().openReadW(&prefixed_path_w); } var buf: [MAX_PATH_BYTES]u8 = undefined; const self_exe_path = try selfExePath(&buf); buf[self_exe_path.len] = 0; - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 - return File.openReadC(@ptrCast([*:0]u8, self_exe_path.ptr)); + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 + return openFileAbsoluteC(@ptrCast([*:0]u8, self_exe_path.ptr), .{}); } test "openSelfExe" { diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 9cfdad3885..540f7d395e 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -25,105 +25,87 @@ pub const File = struct { pub const OpenError = windows.CreateFileError || os.OpenError; - /// Deprecated; call `std.fs.Dir.openRead` directly. + /// TODO https://github.com/ziglang/zig/issues/3802 + pub const OpenFlags = struct { + read: bool = true, + write: bool = false, + }; + + /// TODO https://github.com/ziglang/zig/issues/3802 + pub const CreateFlags = struct { + /// Whether the file will be created with read access. + read: bool = false, + + /// If the file already exists, and is a regular file, and the access + /// mode allows writing, it will be truncated to length 0. + truncate: bool = true, + + /// Ensures that this open call creates the file, otherwise causes + /// `error.FileAlreadyExists` to be returned. + exclusive: bool = false, + + /// For POSIX systems this is the file system mode the file will + /// be created with. + mode: Mode = default_mode, + }; + + /// Deprecated; call `std.fs.Dir.openFile` directly. pub fn openRead(path: []const u8) OpenError!File { - return std.fs.Dir.cwd().openRead(path); + return std.fs.cwd().openFile(path, .{}); } - /// Deprecated; call `std.fs.Dir.openReadC` directly. + /// Deprecated; call `std.fs.Dir.openFileC` directly. pub fn openReadC(path_c: [*:0]const u8) OpenError!File { - return std.fs.Dir.cwd().openReadC(path_c); + return std.fs.cwd().openFileC(path_c, .{}); } - /// Deprecated; call `std.fs.Dir.openReadW` directly. + /// Deprecated; call `std.fs.Dir.openFileW` directly. pub fn openReadW(path_w: [*]const u16) OpenError!File { - return std.fs.Dir.cwd().openReadW(path_w); + return std.fs.cwd().openFileW(path_w, .{}); } - /// Calls `openWriteMode` with `default_mode` for the mode. - /// TODO: deprecate this and move it to `std.fs.Dir`. + /// Deprecated; call `std.fs.Dir.createFile` directly. pub fn openWrite(path: []const u8) OpenError!File { - return openWriteMode(path, default_mode); + return std.fs.cwd().createFile(path, .{}); } - /// If the path does not exist it will be created. - /// If a file already exists in the destination it will be truncated. - /// Call close to clean up. - /// TODO: deprecate this and move it to `std.fs.Dir`. + /// Deprecated; call `std.fs.Dir.createFile` directly. pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File { - if (builtin.os == .windows) { - const path_w = try windows.sliceToPrefixedFileW(path); - return openWriteModeW(&path_w, file_mode); - } - const path_c = try os.toPosixPath(path); - return openWriteModeC(&path_c, file_mode); + return std.fs.cwd().createFile(path, .{ .mode = file_mode }); } - /// Same as `openWriteMode` except `path` is null-terminated. - /// TODO: deprecate this and move it to `std.fs.Dir`. - pub fn openWriteModeC(path: [*:0]const u8, file_mode: Mode) OpenError!File { - if (builtin.os == .windows) { - const path_w = try windows.cStrToPrefixedFileW(path); - return openWriteModeW(&path_w, file_mode); - } - const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; - const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC; - const fd = try os.openC(path, flags, file_mode); - return openHandle(fd); + /// Deprecated; call `std.fs.Dir.createFileC` directly. + pub fn openWriteModeC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File { + return std.fs.cwd().createFileC(path_c, .{ .mode = file_mode }); } - /// Same as `openWriteMode` except `path` is null-terminated and UTF16LE encoded - /// TODO: deprecate this and move it to `std.fs.Dir`. + /// Deprecated; call `std.fs.Dir.createFileW` directly. pub fn openWriteModeW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File { - const handle = try windows.CreateFileW( - path_w, - windows.GENERIC_WRITE, - windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, - null, - windows.CREATE_ALWAYS, - windows.FILE_ATTRIBUTE_NORMAL, - null, - ); - return openHandle(handle); + return std.fs.cwd().createFileW(path_w, .{ .mode = file_mode }); } - /// If the path does not exist it will be created. - /// If a file already exists in the destination this returns OpenError.PathAlreadyExists - /// Call close to clean up. - /// TODO: deprecate this and move it to `std.fs.Dir`. + /// Deprecated; call `std.fs.Dir.createFile` directly. pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File { - if (builtin.os == .windows) { - const path_w = try windows.sliceToPrefixedFileW(path); - return openWriteNoClobberW(&path_w, file_mode); - } - const path_c = try os.toPosixPath(path); - return openWriteNoClobberC(&path_c, file_mode); + return std.fs.cwd().createFile(path, .{ + .mode = file_mode, + .exclusive = true, + }); } - /// TODO: deprecate this and move it to `std.fs.Dir`. - pub fn openWriteNoClobberC(path: [*:0]const u8, file_mode: Mode) OpenError!File { - if (builtin.os == .windows) { - const path_w = try windows.cStrToPrefixedFileW(path); - return openWriteNoClobberW(&path_w, file_mode); - } - const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; - const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_EXCL; - const fd = try os.openC(path, flags, file_mode); - return openHandle(fd); + /// Deprecated; call `std.fs.Dir.createFileC` directly. + pub fn openWriteNoClobberC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File { + return std.fs.cwd().createFileC(path_c, .{ + .mode = file_mode, + .exclusive = true, + }); } - /// TODO: deprecate this and move it to `std.fs.Dir`. + /// Deprecated; call `std.fs.Dir.createFileW` directly. pub fn openWriteNoClobberW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File { - const handle = try windows.CreateFileW( - path_w, - windows.GENERIC_WRITE, - windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, - null, - windows.CREATE_NEW, - windows.FILE_ATTRIBUTE_NORMAL, - null, - ); - return openHandle(handle); + return std.fs.cwd().createFileW(path_w, .{ + .mode = file_mode, + .exclusive = true, + }); } pub fn openHandle(handle: os.fd_t) File { @@ -246,6 +228,7 @@ pub const File = struct { windows.STATUS.SUCCESS => {}, windows.STATUS.BUFFER_OVERFLOW => {}, windows.STATUS.INVALID_PARAMETER => unreachable, + windows.STATUS.ACCESS_DENIED => return error.AccessDenied, else => return windows.unexpectedStatus(rc), } return Stat{ diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index c7ebb470c5..b3ad3e9f7a 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -130,6 +130,14 @@ test "join" { testJoinPosix(&[_][]const u8{ "a/", "/c" }, "a/c"); } +pub fn isAbsoluteC(path_c: [*:0]const u8) bool { + if (builtin.os == .windows) { + return isAbsoluteWindowsC(path_c); + } else { + return isAbsolutePosixC(path_c); + } +} + pub fn isAbsolute(path: []const u8) bool { if (builtin.os == .windows) { return isAbsoluteWindows(path); @@ -138,7 +146,7 @@ pub fn isAbsolute(path: []const u8) bool { } } -pub fn isAbsoluteW(path_w: [*]const u16) bool { +pub fn isAbsoluteW(path_w: [*:0]const u16) bool { if (path_w[0] == '/') return true; @@ -176,10 +184,33 @@ pub fn isAbsoluteWindows(path: []const u8) bool { return false; } +pub fn isAbsoluteWindowsC(path_c: [*:0]const u8) bool { + if (path_c[0] == '/') + return true; + + if (path_c[0] == '\\') { + return true; + } + if (path_c[0] == 0 or path_c[1] == 0 or path_c[2] == 0) { + return false; + } + if (path_c[1] == ':') { + if (path_c[2] == '/') + return true; + if (path_c[2] == '\\') + return true; + } + return false; +} + pub fn isAbsolutePosix(path: []const u8) bool { return path[0] == sep_posix; } +pub fn isAbsolutePosixC(path_c: [*:0]const u8) bool { + return path_c[0] == sep_posix; +} + test "isAbsoluteWindows" { testIsAbsoluteWindows("/", true); testIsAbsoluteWindows("//", true); diff --git a/lib/std/io.zig b/lib/std/io.zig index 09e428984c..8f80fac946 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -61,17 +61,14 @@ pub const COutStream = @import("io/c_out_stream.zig").COutStream; pub const InStream = @import("io/in_stream.zig").InStream; pub const OutStream = @import("io/out_stream.zig").OutStream; -/// TODO move this to `std.fs` and add a version to `std.fs.Dir`. +/// Deprecated; use `std.fs.Dir.writeFile`. pub fn writeFile(path: []const u8, data: []const u8) !void { - var file = try File.openWrite(path); - defer file.close(); - try file.write(data); + return fs.cwd().writeFile(path, data); } -/// On success, caller owns returned buffer. -/// This function is deprecated; use `std.fs.Dir.readFileAlloc`. +/// Deprecated; use `std.fs.Dir.readFileAlloc`. pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 { - return fs.Dir.cwd().readFileAlloc(allocator, path, math.maxInt(usize)); + return fs.cwd().readFileAlloc(allocator, path, math.maxInt(usize)); } pub fn BufferedInStream(comptime Error: type) type { diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index c7471fd315..5b189cbe5a 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -14,12 +14,14 @@ test "write a file, read it, then delete it" { var raw_bytes: [200 * 1024]u8 = undefined; var allocator = &std.heap.FixedBufferAllocator.init(raw_bytes[0..]).allocator; + const cwd = fs.cwd(); + var data: [1024]u8 = undefined; var prng = DefaultPrng.init(1234); prng.random.bytes(data[0..]); const tmp_file_name = "temp_test_file.txt"; { - var file = try File.openWrite(tmp_file_name); + var file = try cwd.createFile(tmp_file_name, .{}); defer file.close(); var file_out_stream = file.outStream(); @@ -32,8 +34,8 @@ test "write a file, read it, then delete it" { } { - // make sure openWriteNoClobber doesn't harm the file - if (File.openWriteNoClobber(tmp_file_name, File.default_mode)) |file| { + // Make sure the exclusive flag is honored. + if (cwd.createFile(tmp_file_name, .{ .exclusive = true })) |file| { unreachable; } else |err| { std.debug.assert(err == File.OpenError.PathAlreadyExists); @@ -41,7 +43,7 @@ test "write a file, read it, then delete it" { } { - var file = try File.openRead(tmp_file_name); + var file = try cwd.openFile(tmp_file_name, .{}); defer file.close(); const file_size = try file.getEndPos(); @@ -58,7 +60,7 @@ test "write a file, read it, then delete it" { expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data)); expect(mem.eql(u8, contents[contents.len - "end".len ..], "end")); } - try fs.deleteFile(tmp_file_name); + try cwd.deleteFile(tmp_file_name); } test "BufferOutStream" { @@ -274,7 +276,7 @@ test "BitOutStream" { test "BitStreams with File Stream" { const tmp_file_name = "temp_test_file.txt"; { - var file = try File.openWrite(tmp_file_name); + var file = try fs.cwd().createFile(tmp_file_name, .{}); defer file.close(); var file_out = file.outStream(); @@ -291,7 +293,7 @@ test "BitStreams with File Stream" { try bit_stream.flushBits(); } { - var file = try File.openRead(tmp_file_name); + var file = try fs.cwd().openFile(tmp_file_name, .{}); defer file.close(); var file_in = file.inStream(); @@ -316,7 +318,7 @@ test "BitStreams with File Stream" { expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1)); } - try fs.deleteFile(tmp_file_name); + try fs.cwd().deleteFile(tmp_file_name); } fn testIntSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { @@ -599,7 +601,7 @@ test "c out stream" { const out_file = std.c.fopen(filename, "w") orelse return error.UnableToOpenTestFile; defer { _ = std.c.fclose(out_file); - fs.deleteFileC(filename) catch {}; + fs.cwd().deleteFileC(filename) catch {}; } const out_stream = &io.COutStream.init(out_file).stream; @@ -608,10 +610,10 @@ test "c out stream" { test "File seek ops" { const tmp_file_name = "temp_test_file.txt"; - var file = try File.openWrite(tmp_file_name); + var file = try fs.cwd().createFile(tmp_file_name, .{}); defer { file.close(); - fs.deleteFile(tmp_file_name) catch {}; + fs.cwd().deleteFile(tmp_file_name) catch {}; } try file.write(&([_]u8{0x55} ** 8192)); @@ -632,10 +634,10 @@ test "File seek ops" { test "updateTimes" { const tmp_file_name = "just_a_temporary_file.txt"; - var file = try File.openWrite(tmp_file_name); + var file = try fs.cwd().createFile(tmp_file_name, .{ .read = true }); defer { file.close(); - std.fs.deleteFile(tmp_file_name) catch {}; + std.fs.cwd().deleteFile(tmp_file_name) catch {}; } var stat_old = try file.stat(); // Set atime and mtime to 5s before diff --git a/lib/std/math.zig b/lib/std/math.zig index 714521357c..f87996f174 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -25,18 +25,6 @@ pub const ln2 = 0.693147180559945309417232121458176568; /// ln(10) pub const ln10 = 2.302585092994045684017991454684364208; -/// π/2 -pub const pi_2 = 1.570796326794896619231321691639751442; - -/// π/4 -pub const pi_4 = 0.785398163397448309615660845819875721; - -/// 1/π -pub const one_pi = 0.318309886183790671537767526745028724; - -/// 2/π -pub const two_pi = 0.636619772367581343075535053490057448; - /// 2/sqrt(π) pub const two_sqrtpi = 1.128379167095512573896158903121545172; diff --git a/lib/std/mutex.zig b/lib/std/mutex.zig index e8f83a4a17..39cfab19ce 100644 --- a/lib/std/mutex.zig +++ b/lib/std/mutex.zig @@ -1,13 +1,12 @@ const std = @import("std.zig"); const builtin = @import("builtin"); const testing = std.testing; -const SpinLock = std.SpinLock; -const ThreadParker = std.ThreadParker; +const ResetEvent = std.ResetEvent; /// Lock may be held only once. If the same thread /// tries to acquire the same mutex twice, it deadlocks. -/// This type supports static initialization and is based off of Golang 1.13 runtime.lock_futex: -/// https://github.com/golang/go/blob/master/src/runtime/lock_futex.go +/// This type supports static initialization and is based off of Webkit's WTF Lock (via rust parking_lot) +/// https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs /// When an application is built in single threaded release mode, all the functions are /// no-ops. In single threaded debug mode, there is deadlock detection. pub const Mutex = if (builtin.single_threaded) @@ -39,80 +38,119 @@ pub const Mutex = if (builtin.single_threaded) } else struct { - state: State, // TODO: make this an enum - parker: ThreadParker, + state: usize, - const State = enum(u32) { - Unlocked, - Sleeping, - Locked, - }; + const MUTEX_LOCK: usize = 1 << 0; + const QUEUE_LOCK: usize = 1 << 1; + const QUEUE_MASK: usize = ~(MUTEX_LOCK | QUEUE_LOCK); + const QueueNode = std.atomic.Stack(ResetEvent).Node; /// number of iterations to spin yielding the cpu const SPIN_CPU = 4; - /// number of iterations to perform in the cpu yield loop + /// number of iterations to spin in the cpu yield loop const SPIN_CPU_COUNT = 30; /// number of iterations to spin yielding the thread const SPIN_THREAD = 1; pub fn init() Mutex { - return Mutex{ - .state = .Unlocked, - .parker = ThreadParker.init(), - }; + return Mutex{ .state = 0 }; } pub fn deinit(self: *Mutex) void { - self.parker.deinit(); + self.* = undefined; } pub const Held = struct { mutex: *Mutex, pub fn release(self: Held) void { - switch (@atomicRmw(State, &self.mutex.state, .Xchg, .Unlocked, .Release)) { - .Locked => {}, - .Sleeping => self.mutex.parker.unpark(@ptrCast(*const u32, &self.mutex.state)), - .Unlocked => unreachable, // unlocking an unlocked mutex - else => unreachable, // should never be anything else + // since MUTEX_LOCK is the first bit, we can use (.Sub) instead of (.And, ~MUTEX_LOCK). + // this is because .Sub may be implemented more efficiently than the latter + // (e.g. `lock xadd` vs `cmpxchg` loop on x86) + const state = @atomicRmw(usize, &self.mutex.state, .Sub, MUTEX_LOCK, .Release); + if ((state & QUEUE_MASK) != 0 and (state & QUEUE_LOCK) == 0) { + self.mutex.releaseSlow(state); } } }; pub fn acquire(self: *Mutex) Held { - // Try and speculatively grab the lock. - // If it fails, the state is either Locked or Sleeping - // depending on if theres a thread stuck sleeping below. - var state = @atomicRmw(State, &self.state, .Xchg, .Locked, .Acquire); - if (state == .Unlocked) - return Held{ .mutex = self }; + // fast path close to SpinLock fast path + if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic)) |current_state| { + self.acquireSlow(current_state); + } + return Held{ .mutex = self }; + } + + fn acquireSlow(self: *Mutex, current_state: usize) void { + var spin: usize = 0; + var state = current_state; + while (true) { + + // try and acquire the lock if unlocked + if ((state & MUTEX_LOCK) == 0) { + state = @cmpxchgWeak(usize, &self.state, state, state | MUTEX_LOCK, .Acquire, .Monotonic) orelse return; + continue; + } + + // spin only if the waiting queue isn't empty and when it hasn't spun too much already + if ((state & QUEUE_MASK) == 0 and spin < SPIN_CPU + SPIN_THREAD) { + if (spin < SPIN_CPU) { + std.SpinLock.yield(SPIN_CPU_COUNT); + } else { + std.os.sched_yield() catch std.time.sleep(0); + } + state = @atomicLoad(usize, &self.state, .Monotonic); + continue; + } + + // thread should block, try and add this event to the waiting queue + var node = QueueNode{ + .next = @intToPtr(?*QueueNode, state & QUEUE_MASK), + .data = ResetEvent.init(), + }; + defer node.data.deinit(); + const new_state = @ptrToInt(&node) | (state & ~QUEUE_MASK); + state = @cmpxchgWeak(usize, &self.state, state, new_state, .Release, .Monotonic) orelse { + // node is in the queue, wait until a `held.release()` wakes us up. + _ = node.data.wait(null) catch unreachable; + spin = 0; + state = @atomicLoad(usize, &self.state, .Monotonic); + continue; + }; + } + } + + fn releaseSlow(self: *Mutex, current_state: usize) void { + // grab the QUEUE_LOCK in order to signal a waiting queue node's event. + var state = current_state; + while (true) { + if ((state & QUEUE_LOCK) != 0 or (state & QUEUE_MASK) == 0) + return; + state = @cmpxchgWeak(usize, &self.state, state, state | QUEUE_LOCK, .Acquire, .Monotonic) orelse break; + } while (true) { - // try and acquire the lock using cpu spinning on failure - var spin: usize = 0; - while (spin < SPIN_CPU) : (spin += 1) { - var value = @atomicLoad(State, &self.state, .Monotonic); - while (value == .Unlocked) - value = @cmpxchgWeak(State, &self.state, .Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self }; - SpinLock.yield(SPIN_CPU_COUNT); + // barrier needed to observe incoming state changes + defer @fence(.Acquire); + + // the mutex is currently locked. try to unset the QUEUE_LOCK and let the locker wake up the next node. + // avoids waking up multiple sleeping threads which try to acquire the lock again which increases contention. + if ((state & MUTEX_LOCK) != 0) { + state = @cmpxchgWeak(usize, &self.state, state, state & ~QUEUE_LOCK, .Release, .Monotonic) orelse return; + continue; } - // try and acquire the lock using thread rescheduling on failure - spin = 0; - while (spin < SPIN_THREAD) : (spin += 1) { - var value = @atomicLoad(State, &self.state, .Monotonic); - while (value == .Unlocked) - value = @cmpxchgWeak(State, &self.state, .Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self }; - std.os.sched_yield() catch std.time.sleep(1); - } - - // failed to acquire the lock, go to sleep until woken up by `Held.release()` - if (@atomicRmw(State, &self.state, .Xchg, .Sleeping, .Acquire) == .Unlocked) - return Held{ .mutex = self }; - state = .Sleeping; - self.parker.park(@ptrCast(*const u32, &self.state), @enumToInt(State.Sleeping)); + // try to pop the top node on the waiting queue stack to wake it up + // while at the same time unsetting the QUEUE_LOCK. + const node = @intToPtr(*QueueNode, state & QUEUE_MASK); + const new_state = @ptrToInt(node.next) | (state & MUTEX_LOCK); + state = @cmpxchgWeak(usize, &self.state, state, new_state, .Release, .Monotonic) orelse { + _ = node.data.set(false); + return; + }; } } }; diff --git a/lib/std/net.zig b/lib/std/net.zig index 7502f1e262..6091564a93 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -812,7 +812,7 @@ fn linuxLookupNameFromHosts( family: os.sa_family_t, port: u16, ) !void { - const file = fs.File.openReadC("/etc/hosts") catch |err| switch (err) { + const file = fs.openFileAbsoluteC("/etc/hosts", .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.AccessDenied, @@ -1006,7 +1006,7 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { }; errdefer rc.deinit(); - const file = fs.File.openReadC("/etc/resolv.conf") catch |err| switch (err) { + const file = fs.openFileAbsoluteC("/etc/resolv.conf", .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.AccessDenied, diff --git a/lib/std/os.zig b/lib/std/os.zig index d137617b89..c06c1dedf5 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -798,7 +798,7 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e path_buf[search_path.len] = '/'; mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice); path_buf[search_path.len + file_slice.len + 1] = 0; - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 err = execveC(@ptrCast([*:0]u8, &path_buf), child_argv, envp); switch (err) { error.AccessDenied => seen_eacces = true, @@ -834,7 +834,7 @@ pub fn execvpe( @memcpy(arg_buf.ptr, arg.ptr, arg.len); arg_buf[arg.len] = 0; - // TODO avoid @ptrCast using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast using slice syntax with https://github.com/ziglang/zig/issues/3770 argv_buf[i] = @ptrCast([*:0]u8, arg_buf.ptr); } argv_buf[argv_slice.len] = null; @@ -842,7 +842,7 @@ pub fn execvpe( const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); defer freeNullDelimitedEnvMap(allocator, envp_buf); - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 const argv_ptr = @ptrCast([*:null]?[*:0]u8, argv_buf.ptr); return execvpeC(argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr); @@ -863,12 +863,12 @@ pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std. @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len); env_buf[env_buf.len - 1] = 0; - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 envp_buf[i] = @ptrCast([*:0]u8, env_buf.ptr); } assert(i == envp_count); } - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 assert(envp_buf[envp_count] == null); return @ptrCast([*:null]?[*:0]u8, envp_buf.ptr)[0..envp_count]; } @@ -1087,7 +1087,9 @@ pub const UnlinkatError = UnlinkError || error{ }; /// Delete a file name and possibly the file it refers to, based on an open directory handle. +/// Asserts that the path parameter has no null bytes. pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { + if (std.debug.runtime_safety) for (file_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return unlinkatW(dirfd, &file_path_w, flags); @@ -2026,7 +2028,10 @@ pub fn waitpid(pid: i32, flags: u32) u32 { } } -pub const FStatError = error{SystemResources} || UnexpectedError; +pub const FStatError = error{ + SystemResources, + AccessDenied, +} || UnexpectedError; pub fn fstat(fd: fd_t) FStatError!Stat { var stat: Stat = undefined; @@ -2036,6 +2041,7 @@ pub fn fstat(fd: fd_t) FStatError!Stat { EINVAL => unreachable, EBADF => unreachable, // Always a race condition. ENOMEM => return error.SystemResources, + EACCES => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -2045,6 +2051,7 @@ pub fn fstat(fd: fd_t) FStatError!Stat { EINVAL => unreachable, EBADF => unreachable, // Always a race condition. ENOMEM => return error.SystemResources, + EACCES => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig index 118223d03c..52cc5a710d 100644 --- a/lib/std/os/bits/linux.zig +++ b/lib/std/os/bits/linux.zig @@ -9,6 +9,7 @@ pub usingnamespace switch (builtin.arch) { }; pub usingnamespace switch (builtin.arch) { + .i386 => @import("linux/i386.zig"), .x86_64 => @import("linux/x86_64.zig"), .aarch64 => @import("linux/arm64.zig"), .arm => @import("linux/arm-eabi.zig"), diff --git a/lib/std/os/bits/linux/arm-eabi.zig b/lib/std/os/bits/linux/arm-eabi.zig index a5b6a31c60..de71607c30 100644 --- a/lib/std/os/bits/linux/arm-eabi.zig +++ b/lib/std/os/bits/linux/arm-eabi.zig @@ -466,7 +466,6 @@ pub const MAP_LOCKED = 0x2000; /// don't check for reservations pub const MAP_NORESERVE = 0x4000; -pub const VDSO_USEFUL = true; pub const VDSO_CGT_SYM = "__vdso_clock_gettime"; pub const VDSO_CGT_VER = "LINUX_2.6"; diff --git a/lib/std/os/bits/linux/arm64.zig b/lib/std/os/bits/linux/arm64.zig index 84f621b07a..8dcebc5ddf 100644 --- a/lib/std/os/bits/linux/arm64.zig +++ b/lib/std/os/bits/linux/arm64.zig @@ -358,7 +358,6 @@ pub const MAP_LOCKED = 0x2000; /// don't check for reservations pub const MAP_NORESERVE = 0x4000; -pub const VDSO_USEFUL = true; pub const VDSO_CGT_SYM = "__kernel_clock_gettime"; pub const VDSO_CGT_VER = "LINUX_2.6.39"; diff --git a/lib/std/os/bits/linux/i386.zig b/lib/std/os/bits/linux/i386.zig new file mode 100644 index 0000000000..2585785b13 --- /dev/null +++ b/lib/std/os/bits/linux/i386.zig @@ -0,0 +1,642 @@ +// i386-specific declarations that are intended to be imported into the POSIX namespace. +// This does include Linux-only APIs. + +const std = @import("../../../std.zig"); +const linux = std.os.linux; +const socklen_t = linux.socklen_t; +const iovec = linux.iovec; +const iovec_const = linux.iovec_const; +const uid_t = linux.uid_t; +const gid_t = linux.gid_t; +const stack_t = linux.stack_t; +const sigset_t = linux.sigset_t; + +pub const SYS_restart_syscall = 0; +pub const SYS_exit = 1; +pub const SYS_fork = 2; +pub const SYS_read = 3; +pub const SYS_write = 4; +pub const SYS_open = 5; +pub const SYS_close = 6; +pub const SYS_waitpid = 7; +pub const SYS_creat = 8; +pub const SYS_link = 9; +pub const SYS_unlink = 10; +pub const SYS_execve = 11; +pub const SYS_chdir = 12; +pub const SYS_time = 13; +pub const SYS_mknod = 14; +pub const SYS_chmod = 15; +pub const SYS_lchown = 16; +pub const SYS_break = 17; +pub const SYS_oldstat = 18; +pub const SYS_lseek = 19; +pub const SYS_getpid = 20; +pub const SYS_mount = 21; +pub const SYS_umount = 22; +pub const SYS_setuid = 23; +pub const SYS_getuid = 24; +pub const SYS_stime = 25; +pub const SYS_ptrace = 26; +pub const SYS_alarm = 27; +pub const SYS_oldfstat = 28; +pub const SYS_pause = 29; +pub const SYS_utime = 30; +pub const SYS_stty = 31; +pub const SYS_gtty = 32; +pub const SYS_access = 33; +pub const SYS_nice = 34; +pub const SYS_ftime = 35; +pub const SYS_sync = 36; +pub const SYS_kill = 37; +pub const SYS_rename = 38; +pub const SYS_mkdir = 39; +pub const SYS_rmdir = 40; +pub const SYS_dup = 41; +pub const SYS_pipe = 42; +pub const SYS_times = 43; +pub const SYS_prof = 44; +pub const SYS_brk = 45; +pub const SYS_setgid = 46; +pub const SYS_getgid = 47; +pub const SYS_signal = 48; +pub const SYS_geteuid = 49; +pub const SYS_getegid = 50; +pub const SYS_acct = 51; +pub const SYS_umount2 = 52; +pub const SYS_lock = 53; +pub const SYS_ioctl = 54; +pub const SYS_fcntl = 55; +pub const SYS_mpx = 56; +pub const SYS_setpgid = 57; +pub const SYS_ulimit = 58; +pub const SYS_oldolduname = 59; +pub const SYS_umask = 60; +pub const SYS_chroot = 61; +pub const SYS_ustat = 62; +pub const SYS_dup2 = 63; +pub const SYS_getppid = 64; +pub const SYS_getpgrp = 65; +pub const SYS_setsid = 66; +pub const SYS_sigaction = 67; +pub const SYS_sgetmask = 68; +pub const SYS_ssetmask = 69; +pub const SYS_setreuid = 70; +pub const SYS_setregid = 71; +pub const SYS_sigsuspend = 72; +pub const SYS_sigpending = 73; +pub const SYS_sethostname = 74; +pub const SYS_setrlimit = 75; +pub const SYS_getrlimit = 76; +pub const SYS_getrusage = 77; +pub const SYS_gettimeofday = 78; +pub const SYS_settimeofday = 79; +pub const SYS_getgroups = 80; +pub const SYS_setgroups = 81; +pub const SYS_select = 82; +pub const SYS_symlink = 83; +pub const SYS_oldlstat = 84; +pub const SYS_readlink = 85; +pub const SYS_uselib = 86; +pub const SYS_swapon = 87; +pub const SYS_reboot = 88; +pub const SYS_readdir = 89; +pub const SYS_mmap = 90; +pub const SYS_munmap = 91; +pub const SYS_truncate = 92; +pub const SYS_ftruncate = 93; +pub const SYS_fchmod = 94; +pub const SYS_fchown = 95; +pub const SYS_getpriority = 96; +pub const SYS_setpriority = 97; +pub const SYS_profil = 98; +pub const SYS_statfs = 99; +pub const SYS_fstatfs = 100; +pub const SYS_ioperm = 101; +pub const SYS_socketcall = 102; +pub const SYS_syslog = 103; +pub const SYS_setitimer = 104; +pub const SYS_getitimer = 105; +pub const SYS_stat = 106; +pub const SYS_lstat = 107; +pub const SYS_fstat = 108; +pub const SYS_olduname = 109; +pub const SYS_iopl = 110; +pub const SYS_vhangup = 111; +pub const SYS_idle = 112; +pub const SYS_vm86old = 113; +pub const SYS_wait4 = 114; +pub const SYS_swapoff = 115; +pub const SYS_sysinfo = 116; +pub const SYS_ipc = 117; +pub const SYS_fsync = 118; +pub const SYS_sigreturn = 119; +pub const SYS_clone = 120; +pub const SYS_setdomainname = 121; +pub const SYS_uname = 122; +pub const SYS_modify_ldt = 123; +pub const SYS_adjtimex = 124; +pub const SYS_mprotect = 125; +pub const SYS_sigprocmask = 126; +pub const SYS_create_module = 127; +pub const SYS_init_module = 128; +pub const SYS_delete_module = 129; +pub const SYS_get_kernel_syms = 130; +pub const SYS_quotactl = 131; +pub const SYS_getpgid = 132; +pub const SYS_fchdir = 133; +pub const SYS_bdflush = 134; +pub const SYS_sysfs = 135; +pub const SYS_personality = 136; +pub const SYS_afs_syscall = 137; +pub const SYS_setfsuid = 138; +pub const SYS_setfsgid = 139; +pub const SYS__llseek = 140; +pub const SYS_getdents = 141; +pub const SYS__newselect = 142; +pub const SYS_flock = 143; +pub const SYS_msync = 144; +pub const SYS_readv = 145; +pub const SYS_writev = 146; +pub const SYS_getsid = 147; +pub const SYS_fdatasync = 148; +pub const SYS__sysctl = 149; +pub const SYS_mlock = 150; +pub const SYS_munlock = 151; +pub const SYS_mlockall = 152; +pub const SYS_munlockall = 153; +pub const SYS_sched_setparam = 154; +pub const SYS_sched_getparam = 155; +pub const SYS_sched_setscheduler = 156; +pub const SYS_sched_getscheduler = 157; +pub const SYS_sched_yield = 158; +pub const SYS_sched_get_priority_max = 159; +pub const SYS_sched_get_priority_min = 160; +pub const SYS_sched_rr_get_interval = 161; +pub const SYS_nanosleep = 162; +pub const SYS_mremap = 163; +pub const SYS_setresuid = 164; +pub const SYS_getresuid = 165; +pub const SYS_vm86 = 166; +pub const SYS_query_module = 167; +pub const SYS_poll = 168; +pub const SYS_nfsservctl = 169; +pub const SYS_setresgid = 170; +pub const SYS_getresgid = 171; +pub const SYS_prctl = 172; +pub const SYS_rt_sigreturn = 173; +pub const SYS_rt_sigaction = 174; +pub const SYS_rt_sigprocmask = 175; +pub const SYS_rt_sigpending = 176; +pub const SYS_rt_sigtimedwait = 177; +pub const SYS_rt_sigqueueinfo = 178; +pub const SYS_rt_sigsuspend = 179; +pub const SYS_pread64 = 180; +pub const SYS_pwrite64 = 181; +pub const SYS_chown = 182; +pub const SYS_getcwd = 183; +pub const SYS_capget = 184; +pub const SYS_capset = 185; +pub const SYS_sigaltstack = 186; +pub const SYS_sendfile = 187; +pub const SYS_getpmsg = 188; +pub const SYS_putpmsg = 189; +pub const SYS_vfork = 190; +pub const SYS_ugetrlimit = 191; +pub const SYS_mmap2 = 192; +pub const SYS_truncate64 = 193; +pub const SYS_ftruncate64 = 194; +pub const SYS_stat64 = 195; +pub const SYS_lstat64 = 196; +pub const SYS_fstat64 = 197; +pub const SYS_lchown32 = 198; +pub const SYS_getuid32 = 199; +pub const SYS_getgid32 = 200; +pub const SYS_geteuid32 = 201; +pub const SYS_getegid32 = 202; +pub const SYS_setreuid32 = 203; +pub const SYS_setregid32 = 204; +pub const SYS_getgroups32 = 205; +pub const SYS_setgroups32 = 206; +pub const SYS_fchown32 = 207; +pub const SYS_setresuid32 = 208; +pub const SYS_getresuid32 = 209; +pub const SYS_setresgid32 = 210; +pub const SYS_getresgid32 = 211; +pub const SYS_chown32 = 212; +pub const SYS_setuid32 = 213; +pub const SYS_setgid32 = 214; +pub const SYS_setfsuid32 = 215; +pub const SYS_setfsgid32 = 216; +pub const SYS_pivot_root = 217; +pub const SYS_mincore = 218; +pub const SYS_madvise = 219; +pub const SYS_getdents64 = 220; +pub const SYS_fcntl64 = 221; +pub const SYS_gettid = 224; +pub const SYS_readahead = 225; +pub const SYS_setxattr = 226; +pub const SYS_lsetxattr = 227; +pub const SYS_fsetxattr = 228; +pub const SYS_getxattr = 229; +pub const SYS_lgetxattr = 230; +pub const SYS_fgetxattr = 231; +pub const SYS_listxattr = 232; +pub const SYS_llistxattr = 233; +pub const SYS_flistxattr = 234; +pub const SYS_removexattr = 235; +pub const SYS_lremovexattr = 236; +pub const SYS_fremovexattr = 237; +pub const SYS_tkill = 238; +pub const SYS_sendfile64 = 239; +pub const SYS_futex = 240; +pub const SYS_sched_setaffinity = 241; +pub const SYS_sched_getaffinity = 242; +pub const SYS_set_thread_area = 243; +pub const SYS_get_thread_area = 244; +pub const SYS_io_setup = 245; +pub const SYS_io_destroy = 246; +pub const SYS_io_getevents = 247; +pub const SYS_io_submit = 248; +pub const SYS_io_cancel = 249; +pub const SYS_fadvise64 = 250; +pub const SYS_exit_group = 252; +pub const SYS_lookup_dcookie = 253; +pub const SYS_epoll_create = 254; +pub const SYS_epoll_ctl = 255; +pub const SYS_epoll_wait = 256; +pub const SYS_remap_file_pages = 257; +pub const SYS_set_tid_address = 258; +pub const SYS_timer_create = 259; +pub const SYS_timer_settime = SYS_timer_create + 1; +pub const SYS_timer_gettime = SYS_timer_create + 2; +pub const SYS_timer_getoverrun = SYS_timer_create + 3; +pub const SYS_timer_delete = SYS_timer_create + 4; +pub const SYS_clock_settime = SYS_timer_create + 5; +pub const SYS_clock_gettime = SYS_timer_create + 6; +pub const SYS_clock_getres = SYS_timer_create + 7; +pub const SYS_clock_nanosleep = SYS_timer_create + 8; +pub const SYS_statfs64 = 268; +pub const SYS_fstatfs64 = 269; +pub const SYS_tgkill = 270; +pub const SYS_utimes = 271; +pub const SYS_fadvise64_64 = 272; +pub const SYS_vserver = 273; +pub const SYS_mbind = 274; +pub const SYS_get_mempolicy = 275; +pub const SYS_set_mempolicy = 276; +pub const SYS_mq_open = 277; +pub const SYS_mq_unlink = SYS_mq_open + 1; +pub const SYS_mq_timedsend = SYS_mq_open + 2; +pub const SYS_mq_timedreceive = SYS_mq_open + 3; +pub const SYS_mq_notify = SYS_mq_open + 4; +pub const SYS_mq_getsetattr = SYS_mq_open + 5; +pub const SYS_kexec_load = 283; +pub const SYS_waitid = 284; +pub const SYS_add_key = 286; +pub const SYS_request_key = 287; +pub const SYS_keyctl = 288; +pub const SYS_ioprio_set = 289; +pub const SYS_ioprio_get = 290; +pub const SYS_inotify_init = 291; +pub const SYS_inotify_add_watch = 292; +pub const SYS_inotify_rm_watch = 293; +pub const SYS_migrate_pages = 294; +pub const SYS_openat = 295; +pub const SYS_mkdirat = 296; +pub const SYS_mknodat = 297; +pub const SYS_fchownat = 298; +pub const SYS_futimesat = 299; +pub const SYS_fstatat64 = 300; +pub const SYS_unlinkat = 301; +pub const SYS_renameat = 302; +pub const SYS_linkat = 303; +pub const SYS_symlinkat = 304; +pub const SYS_readlinkat = 305; +pub const SYS_fchmodat = 306; +pub const SYS_faccessat = 307; +pub const SYS_pselect6 = 308; +pub const SYS_ppoll = 309; +pub const SYS_unshare = 310; +pub const SYS_set_robust_list = 311; +pub const SYS_get_robust_list = 312; +pub const SYS_splice = 313; +pub const SYS_sync_file_range = 314; +pub const SYS_tee = 315; +pub const SYS_vmsplice = 316; +pub const SYS_move_pages = 317; +pub const SYS_getcpu = 318; +pub const SYS_epoll_pwait = 319; +pub const SYS_utimensat = 320; +pub const SYS_signalfd = 321; +pub const SYS_timerfd_create = 322; +pub const SYS_eventfd = 323; +pub const SYS_fallocate = 324; +pub const SYS_timerfd_settime = 325; +pub const SYS_timerfd_gettime = 326; +pub const SYS_signalfd4 = 327; +pub const SYS_eventfd2 = 328; +pub const SYS_epoll_create1 = 329; +pub const SYS_dup3 = 330; +pub const SYS_pipe2 = 331; +pub const SYS_inotify_init1 = 332; +pub const SYS_preadv = 333; +pub const SYS_pwritev = 334; +pub const SYS_rt_tgsigqueueinfo = 335; +pub const SYS_perf_event_open = 336; +pub const SYS_recvmmsg = 337; +pub const SYS_fanotify_init = 338; +pub const SYS_fanotify_mark = 339; +pub const SYS_prlimit64 = 340; +pub const SYS_name_to_handle_at = 341; +pub const SYS_open_by_handle_at = 342; +pub const SYS_clock_adjtime = 343; +pub const SYS_syncfs = 344; +pub const SYS_sendmmsg = 345; +pub const SYS_setns = 346; +pub const SYS_process_vm_readv = 347; +pub const SYS_process_vm_writev = 348; +pub const SYS_kcmp = 349; +pub const SYS_finit_module = 350; +pub const SYS_sched_setattr = 351; +pub const SYS_sched_getattr = 352; +pub const SYS_renameat2 = 353; +pub const SYS_seccomp = 354; +pub const SYS_getrandom = 355; +pub const SYS_memfd_create = 356; +pub const SYS_bpf = 357; +pub const SYS_execveat = 358; +pub const SYS_socket = 359; +pub const SYS_socketpair = 360; +pub const SYS_bind = 361; +pub const SYS_connect = 362; +pub const SYS_listen = 363; +pub const SYS_accept4 = 364; +pub const SYS_getsockopt = 365; +pub const SYS_setsockopt = 366; +pub const SYS_getsockname = 367; +pub const SYS_getpeername = 368; +pub const SYS_sendto = 369; +pub const SYS_sendmsg = 370; +pub const SYS_recvfrom = 371; +pub const SYS_recvmsg = 372; +pub const SYS_shutdown = 373; +pub const SYS_userfaultfd = 374; +pub const SYS_membarrier = 375; +pub const SYS_mlock2 = 376; +pub const SYS_copy_file_range = 377; +pub const SYS_preadv2 = 378; +pub const SYS_pwritev2 = 379; +pub const SYS_pkey_mprotect = 380; +pub const SYS_pkey_alloc = 381; +pub const SYS_pkey_free = 382; +pub const SYS_statx = 383; +pub const SYS_arch_prctl = 384; +pub const SYS_io_pgetevents = 385; +pub const SYS_rseq = 386; +pub const SYS_semget = 393; +pub const SYS_semctl = 394; +pub const SYS_shmget = 395; +pub const SYS_shmctl = 396; +pub const SYS_shmat = 397; +pub const SYS_shmdt = 398; +pub const SYS_msgget = 399; +pub const SYS_msgsnd = 400; +pub const SYS_msgrcv = 401; +pub const SYS_msgctl = 402; +pub const SYS_clock_gettime64 = 403; +pub const SYS_clock_settime64 = 404; +pub const SYS_clock_adjtime64 = 405; +pub const SYS_clock_getres_time64 = 406; +pub const SYS_clock_nanosleep_time64 = 407; +pub const SYS_timer_gettime64 = 408; +pub const SYS_timer_settime64 = 409; +pub const SYS_timerfd_gettime64 = 410; +pub const SYS_timerfd_settime64 = 411; +pub const SYS_utimensat_time64 = 412; +pub const SYS_pselect6_time64 = 413; +pub const SYS_ppoll_time64 = 414; +pub const SYS_io_pgetevents_time64 = 416; +pub const SYS_recvmmsg_time64 = 417; +pub const SYS_mq_timedsend_time64 = 418; +pub const SYS_mq_timedreceive_time64 = 419; +pub const SYS_semtimedop_time64 = 420; +pub const SYS_rt_sigtimedwait_time64 = 421; +pub const SYS_futex_time64 = 422; +pub const SYS_sched_rr_get_interval_time64 = 423; +pub const SYS_pidfd_send_signal = 424; +pub const SYS_io_uring_setup = 425; +pub const SYS_io_uring_enter = 426; +pub const SYS_io_uring_register = 427; +pub const SYS_open_tree = 428; +pub const SYS_move_mount = 429; +pub const SYS_fsopen = 430; +pub const SYS_fsconfig = 431; +pub const SYS_fsmount = 432; +pub const SYS_fspick = 433; + +pub const O_CREAT = 0o100; +pub const O_EXCL = 0o200; +pub const O_NOCTTY = 0o400; +pub const O_TRUNC = 0o1000; +pub const O_APPEND = 0o2000; +pub const O_NONBLOCK = 0o4000; +pub const O_DSYNC = 0o10000; +pub const O_SYNC = 0o4010000; +pub const O_RSYNC = 0o4010000; +pub const O_DIRECTORY = 0o200000; +pub const O_NOFOLLOW = 0o400000; +pub const O_CLOEXEC = 0o2000000; + +pub const O_ASYNC = 0o20000; +pub const O_DIRECT = 0o40000; +pub const O_LARGEFILE = 0o100000; +pub const O_NOATIME = 0o1000000; +pub const O_PATH = 0o10000000; +pub const O_TMPFILE = 0o20200000; +pub const O_NDELAY = O_NONBLOCK; + +pub const F_DUPFD = 0; +pub const F_GETFD = 1; +pub const F_SETFD = 2; +pub const F_GETFL = 3; +pub const F_SETFL = 4; + +pub const F_SETOWN = 8; +pub const F_GETOWN = 9; +pub const F_SETSIG = 10; +pub const F_GETSIG = 11; + +pub const F_GETLK = 12; +pub const F_SETLK = 13; +pub const F_SETLKW = 14; + +pub const F_SETOWN_EX = 15; +pub const F_GETOWN_EX = 16; + +pub const F_GETOWNER_UIDS = 17; + +pub const MAP_NORESERVE = 0x4000; +pub const MAP_GROWSDOWN = 0x0100; +pub const MAP_DENYWRITE = 0x0800; +pub const MAP_EXECUTABLE = 0x1000; +pub const MAP_LOCKED = 0x2000; +pub const MAP_32BIT = 0x40; + +pub const MMAP2_UNIT = 4096; + +pub const VDSO_CGT_SYM = "__vdso_clock_gettime"; +pub const VDSO_CGT_VER = "LINUX_2.6"; + +pub const msghdr = extern struct { + msg_name: ?*sockaddr, + msg_namelen: socklen_t, + msg_iov: [*]iovec, + msg_iovlen: i32, + msg_control: ?*c_void, + msg_controllen: socklen_t, + msg_flags: i32, +}; + +pub const msghdr_const = extern struct { + msg_name: ?*const sockaddr, + msg_namelen: socklen_t, + msg_iov: [*]iovec_const, + msg_iovlen: i32, + msg_control: ?*c_void, + msg_controllen: socklen_t, + msg_flags: i32, +}; + +pub const blksize_t = i32; +pub const nlink_t = u32; +pub const time_t = isize; +pub const mode_t = u32; +pub const off_t = i64; +pub const ino_t = u64; +pub const dev_t = u64; +pub const blkcnt_t = i64; + +/// Renamed to Stat to not conflict with the stat function. +/// atime, mtime, and ctime have functions to return `timespec`, +/// because although this is a POSIX API, the layout and names of +/// the structs are inconsistent across operating systems, and +/// in C, macros are used to hide the differences. Here we use +/// methods to accomplish this. +pub const Stat = extern struct { + dev: dev_t, + __dev_padding: u32, + __ino_truncated: u32, + mode: mode_t, + nlink: nlink_t, + uid: uid_t, + gid: gid_t, + rdev: dev_t, + __rdev_padding: u32, + size: off_t, + blksize: blksize_t, + blocks: blkcnt_t, + atim: timespec, + mtim: timespec, + ctim: timespec, + ino: ino_t, + + pub fn atime(self: Stat) timespec { + return self.atim; + } + + pub fn mtime(self: Stat) timespec { + return self.mtim; + } + + pub fn ctime(self: Stat) timespec { + return self.ctim; + } +}; + +pub const timespec = extern struct { + tv_sec: i32, + tv_nsec: i32, +}; + +pub const timeval = extern struct { + tv_sec: i32, + tv_usec: i32, +}; + +pub const timezone = extern struct { + tz_minuteswest: i32, + tz_dsttime: i32, +}; + +pub const mcontext_t = extern struct { + gregs: [19]usize, + fpregs: [*]u8, + oldmask: usize, + cr2: usize, +}; + +pub const REG_GS = 0; +pub const REG_FS = 1; +pub const REG_ES = 2; +pub const REG_DS = 3; +pub const REG_EDI = 4; +pub const REG_ESI = 5; +pub const REG_EBP = 6; +pub const REG_ESP = 7; +pub const REG_EBX = 8; +pub const REG_EDX = 9; +pub const REG_ECX = 10; +pub const REG_EAX = 11; +pub const REG_TRAPNO = 12; +pub const REG_ERR = 13; +pub const REG_EIP = 14; +pub const REG_CS = 15; +pub const REG_EFL = 16; +pub const REG_UESP = 17; +pub const REG_SS = 18; + +pub const ucontext_t = extern struct { + flags: usize, + link: *ucontext_t, + stack: stack_t, + mcontext: mcontext_t, + sigmask: sigset_t, + regspace: [64]u64, +}; + +pub const Elf_Symndx = u32; + +pub const user_desc = packed struct { + entry_number: u32, + base_addr: u32, + limit: u32, + seg_32bit: u1, + contents: u2, + read_exec_only: u1, + limit_in_pages: u1, + seg_not_present: u1, + useable: u1, +}; + +// socketcall() call numbers +pub const SC_socket = 1; +pub const SC_bind = 2; +pub const SC_connect = 3; +pub const SC_listen = 4; +pub const SC_accept = 5; +pub const SC_getsockname = 6; +pub const SC_getpeername = 7; +pub const SC_socketpair = 8; +pub const SC_send = 9; +pub const SC_recv = 10; +pub const SC_sendto = 11; +pub const SC_recvfrom = 12; +pub const SC_shutdown = 13; +pub const SC_setsockopt = 14; +pub const SC_getsockopt = 15; +pub const SC_sendmsg = 16; +pub const SC_recvmsg = 17; +pub const SC_accept4 = 18; +pub const SC_recvmmsg = 19; +pub const SC_sendmmsg = 20; diff --git a/lib/std/os/bits/linux/mipsel.zig b/lib/std/os/bits/linux/mipsel.zig index c3821791ce..638a4b1de7 100644 --- a/lib/std/os/bits/linux/mipsel.zig +++ b/lib/std/os/bits/linux/mipsel.zig @@ -454,7 +454,6 @@ pub const SO_PEERSEC = 30; pub const SO_SNDBUFFORCE = 31; pub const SO_RCVBUFFORCE = 33; -pub const VDSO_USEFUL = true; pub const VDSO_CGT_SYM = "__kernel_clock_gettime"; pub const VDSO_CGT_VER = "LINUX_2.6.39"; diff --git a/lib/std/os/bits/linux/x86_64.zig b/lib/std/os/bits/linux/x86_64.zig index 7b1ad4dd5c..da3caf2c88 100644 --- a/lib/std/os/bits/linux/x86_64.zig +++ b/lib/std/os/bits/linux/x86_64.zig @@ -420,7 +420,6 @@ pub const MAP_LOCKED = 0x2000; /// don't check for reservations pub const MAP_NORESERVE = 0x4000; -pub const VDSO_USEFUL = true; pub const VDSO_CGT_SYM = "__vdso_clock_gettime"; pub const VDSO_CGT_VER = "LINUX_2.6"; pub const VDSO_GETCPU_SYM = "__vdso_getcpu"; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 1de66b8d2e..e3d84e1e63 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -14,6 +14,7 @@ const vdso = @import("linux/vdso.zig"); const dl = @import("../dynamic_library.zig"); pub usingnamespace switch (builtin.arch) { + .i386 => @import("linux/i386.zig"), .x86_64 => @import("linux/x86_64.zig"), .aarch64 => @import("linux/arm64.zig"), .arm => @import("linux/arm-eabi.zig"), @@ -743,26 +744,44 @@ pub fn sigismember(set: *const sigset_t, sig: u6) bool { } pub fn getsockname(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize { + if (builtin.arch == .i386) { + return socketcall(SC_getsockname, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), @ptrToInt(len) }); + } return syscall3(SYS_getsockname, @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), @ptrToInt(len)); } pub fn getpeername(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize { + if (builtin.arch == .i386) { + return socketcall(SC_getpeername, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), @ptrToInt(len) }); + } return syscall3(SYS_getpeername, @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), @ptrToInt(len)); } pub fn socket(domain: u32, socket_type: u32, protocol: u32) usize { + if (builtin.arch == .i386) { + return socketcall(SC_socket, &[3]usize{ domain, socket_type, protocol }); + } return syscall3(SYS_socket, domain, socket_type, protocol); } pub fn setsockopt(fd: i32, level: u32, optname: u32, optval: [*]const u8, optlen: socklen_t) usize { + if (builtin.arch == .i386) { + return socketcall(SC_setsockopt, &[5]usize{ @bitCast(usize, @as(isize, fd)), level, optname, @ptrToInt(optval), @intCast(usize, optlen) }); + } return syscall5(SYS_setsockopt, @bitCast(usize, @as(isize, fd)), level, optname, @ptrToInt(optval), @intCast(usize, optlen)); } pub fn getsockopt(fd: i32, level: u32, optname: u32, noalias optval: [*]u8, noalias optlen: *socklen_t) usize { + if (builtin.arch == .i386) { + return socketcall(SC_getsockopt, &[5]usize{ @bitCast(usize, @as(isize, fd)), level, optname, @ptrToInt(optval), @ptrToInt(optlen) }); + } return syscall5(SYS_getsockopt, @bitCast(usize, @as(isize, fd)), level, optname, @ptrToInt(optval), @ptrToInt(optlen)); } pub fn sendmsg(fd: i32, msg: *msghdr_const, flags: u32) usize { + if (builtin.arch == .i386) { + return socketcall(SC_sendmsg, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), flags }); + } return syscall3(SYS_sendmsg, @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), flags); } @@ -807,42 +826,72 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize } pub fn connect(fd: i32, addr: *const c_void, len: socklen_t) usize { + if (builtin.arch == .i386) { + return socketcall(SC_connect, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), len }); + } return syscall3(SYS_connect, @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), len); } pub fn recvmsg(fd: i32, msg: *msghdr, flags: u32) usize { + if (builtin.arch == .i386) { + return socketcall(SC_recvmsg, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), flags }); + } return syscall3(SYS_recvmsg, @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), flags); } pub fn recvfrom(fd: i32, noalias buf: [*]u8, len: usize, flags: u32, noalias addr: ?*sockaddr, noalias alen: ?*socklen_t) usize { + if (builtin.arch == .i386) { + return socketcall(SC_recvfrom, &[6]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen) }); + } return syscall6(SYS_recvfrom, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen)); } pub fn shutdown(fd: i32, how: i32) usize { + if (builtin.arch == .i386) { + return socketcall(SC_shutdown, &[2]usize{ @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, how)) }); + } return syscall2(SYS_shutdown, @bitCast(usize, @as(isize, fd)), @bitCast(usize, @as(isize, how))); } pub fn bind(fd: i32, addr: *const sockaddr, len: socklen_t) usize { + if (builtin.arch == .i386) { + return socketcall(SC_bind, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), @intCast(usize, len) }); + } return syscall3(SYS_bind, @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), @intCast(usize, len)); } pub fn listen(fd: i32, backlog: u32) usize { + if (builtin.arch == .i386) { + return socketcall(SC_listen, &[2]usize{ @bitCast(usize, @as(isize, fd)), backlog }); + } return syscall2(SYS_listen, @bitCast(usize, @as(isize, fd)), backlog); } pub fn sendto(fd: i32, buf: [*]const u8, len: usize, flags: u32, addr: ?*const sockaddr, alen: socklen_t) usize { + if (builtin.arch == .i386) { + return socketcall(SC_sendto, &[6]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @intCast(usize, alen) }); + } return syscall6(SYS_sendto, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @intCast(usize, alen)); } pub fn socketpair(domain: i32, socket_type: i32, protocol: i32, fd: [2]i32) usize { + if (builtin.arch == .i386) { + return socketcall(SC_socketpair, &[4]usize{ @intCast(usize, domain), @intCast(usize, socket_type), @intCast(usize, protocol), @ptrToInt(&fd[0]) }); + } return syscall4(SYS_socketpair, @intCast(usize, domain), @intCast(usize, socket_type), @intCast(usize, protocol), @ptrToInt(&fd[0])); } pub fn accept(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize { + if (builtin.arch == .i386) { + return socketcall(SC_accept, &[4]usize{ fd, addr, len, 0 }); + } return accept4(fd, addr, len, 0); } pub fn accept4(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t, flags: u32) usize { + if (builtin.arch == .i386) { + return socketcall(SC_accept4, &[4]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), @ptrToInt(len), flags }); + } return syscall4(SYS_accept4, @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), @ptrToInt(len), flags); } diff --git a/lib/std/os/linux/i386.zig b/lib/std/os/linux/i386.zig new file mode 100644 index 0000000000..3345f9904d --- /dev/null +++ b/lib/std/os/linux/i386.zig @@ -0,0 +1,119 @@ +usingnamespace @import("../bits.zig"); + +pub fn syscall0(number: usize) usize { + return asm volatile ("int $0x80" + : [ret] "={eax}" (-> usize) + : [number] "{eax}" (number) + : "memory" + ); +} + +pub fn syscall1(number: usize, arg1: usize) usize { + return asm volatile ("int $0x80" + : [ret] "={eax}" (-> usize) + : [number] "{eax}" (number), + [arg1] "{ebx}" (arg1) + : "memory" + ); +} + +pub fn syscall2(number: usize, arg1: usize, arg2: usize) usize { + return asm volatile ("int $0x80" + : [ret] "={eax}" (-> usize) + : [number] "{eax}" (number), + [arg1] "{ebx}" (arg1), + [arg2] "{ecx}" (arg2) + : "memory" + ); +} + +pub fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) usize { + return asm volatile ("int $0x80" + : [ret] "={eax}" (-> usize) + : [number] "{eax}" (number), + [arg1] "{ebx}" (arg1), + [arg2] "{ecx}" (arg2), + [arg3] "{edx}" (arg3) + : "memory" + ); +} + +pub fn syscall4(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize) usize { + return asm volatile ("int $0x80" + : [ret] "={eax}" (-> usize) + : [number] "{eax}" (number), + [arg1] "{ebx}" (arg1), + [arg2] "{ecx}" (arg2), + [arg3] "{edx}" (arg3), + [arg4] "{esi}" (arg4) + : "memory" + ); +} + +pub fn syscall5(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) usize { + return asm volatile ("int $0x80" + : [ret] "={eax}" (-> usize) + : [number] "{eax}" (number), + [arg1] "{ebx}" (arg1), + [arg2] "{ecx}" (arg2), + [arg3] "{edx}" (arg3), + [arg4] "{esi}" (arg4), + [arg5] "{edi}" (arg5) + : "memory" + ); +} + +pub fn syscall6( + number: usize, + arg1: usize, + arg2: usize, + arg3: usize, + arg4: usize, + arg5: usize, + arg6: usize, +) usize { + return asm volatile ( + \\ push %%ebp + \\ mov %[arg6], %%ebp + \\ int $0x80 + \\ pop %%ebp + : [ret] "={eax}" (-> usize) + : [number] "{eax}" (number), + [arg1] "{ebx}" (arg1), + [arg2] "{ecx}" (arg2), + [arg3] "{edx}" (arg3), + [arg4] "{esi}" (arg4), + [arg5] "{edi}" (arg5), + [arg6] "rm" (arg6) + : "memory" + ); +} + +pub fn socketcall(call: usize, args: [*]usize) usize { + return asm volatile ("int $0x80" + : [ret] "={eax}" (-> usize) + : [number] "{eax}" (@as(usize, SYS_socketcall)), + [arg1] "{ebx}" (call), + [arg2] "{ecx}" (@ptrToInt(args)) + : "memory" + ); +} + +/// This matches the libc clone function. +pub extern fn clone(func: extern fn (arg: usize) u8, stack: usize, flags: u32, arg: usize, ptid: *i32, tls: usize, ctid: *i32) usize; + +pub nakedcc fn restore() void { + return asm volatile ("int $0x80" + : + : [number] "{eax}" (@as(usize, SYS_sigreturn)) + : "memory" + ); +} + +pub nakedcc fn restore_rt() void { + return asm volatile ("int $0x80" + : + : [number] "{eax}" (@as(usize, SYS_rt_sigreturn)) + : "memory" + ); +} diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index 8281851d6b..aa6655b7d8 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -4,6 +4,7 @@ const linux = std.os.linux; const mem = std.mem; const elf = std.elf; const expect = std.testing.expect; +const fs = std.fs; test "getpid" { expect(linux.getpid() != 0); @@ -45,14 +46,12 @@ test "timer" { err = linux.epoll_wait(@intCast(i32, epoll_fd), @ptrCast([*]linux.epoll_event, &events), 8, -1); } -const File = std.fs.File; - test "statx" { const tmp_file_name = "just_a_temporary_file.txt"; - var file = try File.openWrite(tmp_file_name); + var file = try fs.cwd().createFile(tmp_file_name, .{}); defer { file.close(); - std.fs.deleteFile(tmp_file_name) catch {}; + fs.cwd().deleteFile(tmp_file_name) catch {}; } var statx_buf: linux.Statx = undefined; diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index 849a9ef405..c340a977e0 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -109,12 +109,38 @@ const TLSImage = struct { tcb_offset: usize, dtv_offset: usize, data_offset: usize, + // Only used on the i386 architecture + gdt_entry_number: usize, }; pub var tls_image: ?TLSImage = null; pub fn setThreadPointer(addr: usize) void { switch (builtin.arch) { + .i386 => { + var user_desc = std.os.linux.user_desc{ + .entry_number = tls_image.?.gdt_entry_number, + .base_addr = addr, + .limit = 0xfffff, + .seg_32bit = 1, + .contents = 0, // Data + .read_exec_only = 0, + .limit_in_pages = 1, + .seg_not_present = 0, + .useable = 1, + }; + const rc = std.os.linux.syscall1(std.os.linux.SYS_set_thread_area, @ptrToInt(&user_desc)); + assert(rc == 0); + + const gdt_entry_number = user_desc.entry_number; + // We have to keep track of our slot as it's also needed for clone() + tls_image.?.gdt_entry_number = gdt_entry_number; + // Update the %gs selector + asm volatile ("movl %[gs_val], %%gs" + : + : [gs_val] "r" (gdt_entry_number << 3 | 3) + ); + }, .x86_64 => { const rc = std.os.linux.syscall2(std.os.linux.SYS_arch_prctl, std.os.linux.ARCH_SET_FS, addr); assert(rc == 0); @@ -238,6 +264,7 @@ pub fn initTLS() ?*elf.Phdr { .tcb_offset = tcb_offset, .dtv_offset = dtv_offset, .data_offset = data_offset, + .gdt_entry_number = @bitCast(usize, @as(isize, -1)), }; } diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 8fff83d8c7..f61e8b4657 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -20,7 +20,7 @@ test "makePath, put some files in it, deleteTree" { try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); try fs.deleteTree("os_test_tmp"); - if (fs.Dir.cwd().openDirTraverse("os_test_tmp")) |dir| { + if (fs.cwd().openDirTraverse("os_test_tmp")) |dir| { @panic("expected error"); } else |err| { expect(err == error.FileNotFound); @@ -111,7 +111,7 @@ test "AtomicFile" { const content = try io.readFileAlloc(allocator, test_out_file); expect(mem.eql(u8, content, test_content)); - try fs.deleteFile(test_out_file); + try fs.cwd().deleteFile(test_out_file); } test "thread local storage" { diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig index c417097c7b..6c981dfc72 100644 --- a/lib/std/os/wasi.zig +++ b/lib/std/os/wasi.zig @@ -12,8 +12,8 @@ comptime { assert(@alignOf(u16) == 2); assert(@alignOf(i32) == 4); assert(@alignOf(u32) == 4); - assert(@alignOf(i64) == 8); - assert(@alignOf(u64) == 8); + // assert(@alignOf(i64) == 8); + // assert(@alignOf(u64) == 8); } pub const iovec_t = iovec; diff --git a/lib/std/parker.zig b/lib/std/parker.zig deleted file mode 100644 index 4ba0100b9e..0000000000 --- a/lib/std/parker.zig +++ /dev/null @@ -1,180 +0,0 @@ -const std = @import("std.zig"); -const builtin = @import("builtin"); -const time = std.time; -const testing = std.testing; -const assert = std.debug.assert; -const SpinLock = std.SpinLock; -const linux = std.os.linux; -const windows = std.os.windows; - -pub const ThreadParker = switch (builtin.os) { - .linux => if (builtin.link_libc) PosixParker else LinuxParker, - .windows => WindowsParker, - else => if (builtin.link_libc) PosixParker else SpinParker, -}; - -const SpinParker = struct { - pub fn init() SpinParker { - return SpinParker{}; - } - pub fn deinit(self: *SpinParker) void {} - - pub fn unpark(self: *SpinParker, ptr: *const u32) void {} - - pub fn park(self: *SpinParker, ptr: *const u32, expected: u32) void { - var backoff = SpinLock.Backoff.init(); - while (@atomicLoad(u32, ptr, .Acquire) == expected) - backoff.yield(); - } -}; - -const LinuxParker = struct { - pub fn init() LinuxParker { - return LinuxParker{}; - } - pub fn deinit(self: *LinuxParker) void {} - - pub fn unpark(self: *LinuxParker, ptr: *const u32) void { - const rc = linux.futex_wake(@ptrCast(*const i32, ptr), linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1); - assert(linux.getErrno(rc) == 0); - } - - pub fn park(self: *LinuxParker, ptr: *const u32, expected: u32) void { - const value = @intCast(i32, expected); - while (@atomicLoad(u32, ptr, .Acquire) == expected) { - const rc = linux.futex_wait(@ptrCast(*const i32, ptr), linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, value, null); - switch (linux.getErrno(rc)) { - 0, linux.EAGAIN => return, - linux.EINTR => continue, - linux.EINVAL => unreachable, - else => continue, - } - } - } -}; - -const WindowsParker = struct { - waiters: u32, - - pub fn init() WindowsParker { - return WindowsParker{ .waiters = 0 }; - } - pub fn deinit(self: *WindowsParker) void {} - - pub fn unpark(self: *WindowsParker, ptr: *const u32) void { - const key = @ptrCast(*const c_void, ptr); - const handle = getEventHandle() orelse return; - - var waiting = @atomicLoad(u32, &self.waiters, .Monotonic); - while (waiting != 0) { - waiting = @cmpxchgWeak(u32, &self.waiters, waiting, waiting - 1, .Acquire, .Monotonic) orelse { - const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); - assert(rc == 0); - return; - }; - } - } - - pub fn park(self: *WindowsParker, ptr: *const u32, expected: u32) void { - var spin = SpinLock.Backoff.init(); - const ev_handle = getEventHandle(); - const key = @ptrCast(*const c_void, ptr); - - while (@atomicLoad(u32, ptr, .Monotonic) == expected) { - if (ev_handle) |handle| { - _ = @atomicRmw(u32, &self.waiters, .Add, 1, .Release); - const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null); - assert(rc == 0); - } else { - spin.yield(); - } - } - } - - var event_handle = std.lazyInit(windows.HANDLE); - - fn getEventHandle() ?windows.HANDLE { - if (event_handle.get()) |handle_ptr| - return handle_ptr.*; - defer event_handle.resolve(); - - const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE; - if (windows.ntdll.NtCreateKeyedEvent(&event_handle.data, access_mask, null, 0) != 0) - return null; - return event_handle.data; - } -}; - -const PosixParker = struct { - cond: c.pthread_cond_t, - mutex: c.pthread_mutex_t, - - const c = std.c; - - pub fn init() PosixParker { - return PosixParker{ - .cond = c.PTHREAD_COND_INITIALIZER, - .mutex = c.PTHREAD_MUTEX_INITIALIZER, - }; - } - - pub fn deinit(self: *PosixParker) void { - // On dragonfly, the destroy functions return EINVAL if they were initialized statically. - const retm = c.pthread_mutex_destroy(&self.mutex); - assert(retm == 0 or retm == (if (builtin.os == .dragonfly) os.EINVAL else 0)); - const retc = c.pthread_cond_destroy(&self.cond); - assert(retc == 0 or retc == (if (builtin.os == .dragonfly) os.EINVAL else 0)); - } - - pub fn unpark(self: *PosixParker, ptr: *const u32) void { - assert(c.pthread_mutex_lock(&self.mutex) == 0); - defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); - assert(c.pthread_cond_signal(&self.cond) == 0); - } - - pub fn park(self: *PosixParker, ptr: *const u32, expected: u32) void { - assert(c.pthread_mutex_lock(&self.mutex) == 0); - defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); - while (@atomicLoad(u32, ptr, .Acquire) == expected) - assert(c.pthread_cond_wait(&self.cond, &self.mutex) == 0); - } -}; - -test "std.ThreadParker" { - if (builtin.single_threaded) - return error.SkipZigTest; - - const Context = struct { - parker: ThreadParker, - data: u32, - - fn receiver(self: *@This()) void { - self.parker.park(&self.data, 0); // receives 1 - assert(@atomicRmw(u32, &self.data, .Xchg, 2, .SeqCst) == 1); // sends 2 - self.parker.unpark(&self.data); // wakes up waiters on 2 - self.parker.park(&self.data, 2); // receives 3 - assert(@atomicRmw(u32, &self.data, .Xchg, 4, .SeqCst) == 3); // sends 4 - self.parker.unpark(&self.data); // wakes up waiters on 4 - } - - fn sender(self: *@This()) void { - assert(@atomicRmw(u32, &self.data, .Xchg, 1, .SeqCst) == 0); // sends 1 - self.parker.unpark(&self.data); // wakes up waiters on 1 - self.parker.park(&self.data, 1); // receives 2 - assert(@atomicRmw(u32, &self.data, .Xchg, 3, .SeqCst) == 2); // sends 3 - self.parker.unpark(&self.data); // wakes up waiters on 3 - self.parker.park(&self.data, 3); // receives 4 - } - }; - - var context = Context{ - .parker = ThreadParker.init(), - .data = 0, - }; - defer context.parker.deinit(); - - var receiver = try std.Thread.spawn(&context, Context.receiver); - defer receiver.wait(); - - context.sender(); -} diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index aef68631b7..4b9b3a2a65 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -6,6 +6,7 @@ const mem = std.mem; const os = std.os; const warn = std.debug.warn; const coff = std.coff; +const fs = std.fs; const File = std.fs.File; const ArrayList = std.ArrayList; @@ -469,7 +470,7 @@ pub const Pdb = struct { msf: Msf, pub fn openFile(self: *Pdb, coff_ptr: *coff.Coff, file_name: []u8) !void { - self.in_file = try File.openRead(file_name); + self.in_file = try fs.cwd().openFile(file_name, .{}); self.allocator = coff_ptr.allocator; self.coff = coff_ptr; diff --git a/lib/std/reset_event.zig b/lib/std/reset_event.zig new file mode 100644 index 0000000000..e408c0d0ac --- /dev/null +++ b/lib/std/reset_event.zig @@ -0,0 +1,433 @@ +const std = @import("std.zig"); +const builtin = @import("builtin"); +const testing = std.testing; +const assert = std.debug.assert; +const Backoff = std.SpinLock.Backoff; +const c = std.c; +const os = std.os; +const time = std.time; +const linux = os.linux; +const windows = os.windows; + +/// A resource object which supports blocking until signaled. +/// Once finished, the `deinit()` method should be called for correctness. +pub const ResetEvent = struct { + os_event: OsEvent, + + pub fn init() ResetEvent { + return ResetEvent{ .os_event = OsEvent.init() }; + } + + pub fn deinit(self: *ResetEvent) void { + self.os_event.deinit(); + self.* = undefined; + } + + /// Returns whether or not the event is currenetly set + pub fn isSet(self: *ResetEvent) bool { + return self.os_event.isSet(); + } + + /// Sets the event if not already set and + /// wakes up AT LEAST one thread waiting the event. + /// Returns whether or not a thread was woken up. + pub fn set(self: *ResetEvent, auto_reset: bool) bool { + return self.os_event.set(auto_reset); + } + + /// Resets the event to its original, unset state. + /// Returns whether or not the event was currently set before un-setting. + pub fn reset(self: *ResetEvent) bool { + return self.os_event.reset(); + } + + const WaitError = error{ + /// The thread blocked longer than the maximum time specified. + TimedOut, + }; + + /// Wait for the event to be set by blocking the current thread. + /// Optionally provided timeout in nanoseconds which throws an + /// `error.TimedOut` if the thread blocked AT LEAST longer than specified. + /// Returns whether or not the thread blocked from the event being unset at the time of calling. + pub fn wait(self: *ResetEvent, timeout_ns: ?u64) WaitError!bool { + return self.os_event.wait(timeout_ns); + } +}; + +const OsEvent = if (builtin.single_threaded) DebugEvent else switch (builtin.os) { + .windows => WindowsEvent, + .linux => if (builtin.link_libc) PosixEvent else LinuxEvent, + else => if (builtin.link_libc) PosixEvent else SpinEvent, +}; + +const DebugEvent = struct { + is_set: @typeOf(set_init), + + const set_init = if (std.debug.runtime_safety) false else {}; + + pub fn init() DebugEvent { + return DebugEvent{ .is_set = set_init }; + } + + pub fn deinit(self: *DebugEvent) void { + self.* = undefined; + } + + pub fn isSet(self: *DebugEvent) bool { + if (!std.debug.runtime_safety) + return true; + return self.is_set; + } + + pub fn set(self: *DebugEvent, auto_reset: bool) bool { + if (std.debug.runtime_safety) + self.is_set = !auto_reset; + return false; + } + + pub fn reset(self: *DebugEvent) bool { + if (!std.debug.runtime_safety) + return false; + const was_set = self.is_set; + self.is_set = false; + return was_set; + } + + pub fn wait(self: *DebugEvent, timeout: ?u64) ResetEvent.WaitError!bool { + if (std.debug.runtime_safety and !self.is_set) + @panic("deadlock detected"); + return ResetEvent.WaitError.TimedOut; + } +}; + +fn AtomicEvent(comptime FutexImpl: type) type { + return struct { + state: u32, + + const IS_SET: u32 = 1 << 0; + const WAIT_MASK = ~IS_SET; + + pub const Self = @This(); + pub const Futex = FutexImpl; + + pub fn init() Self { + return Self{ .state = 0 }; + } + + pub fn deinit(self: *Self) void { + self.* = undefined; + } + + pub fn isSet(self: *const Self) bool { + const state = @atomicLoad(u32, &self.state, .Acquire); + return (state & IS_SET) != 0; + } + + pub fn reset(self: *Self) bool { + const old_state = @atomicRmw(u32, &self.state, .Xchg, 0, .Monotonic); + return (old_state & IS_SET) != 0; + } + + pub fn set(self: *Self, auto_reset: bool) bool { + const new_state = if (auto_reset) 0 else IS_SET; + const old_state = @atomicRmw(u32, &self.state, .Xchg, new_state, .Release); + if ((old_state & WAIT_MASK) == 0) { + return false; + } + + Futex.wake(&self.state); + return true; + } + + pub fn wait(self: *Self, timeout: ?u64) ResetEvent.WaitError!bool { + var dummy_value: u32 = undefined; + const wait_token = @truncate(u32, @ptrToInt(&dummy_value)); + + var state = @atomicLoad(u32, &self.state, .Monotonic); + while (true) { + if ((state & IS_SET) != 0) + return false; + state = @cmpxchgWeak(u32, &self.state, state, wait_token, .Acquire, .Monotonic) orelse break; + } + + try Futex.wait(&self.state, wait_token, timeout); + return true; + } + }; +} + +const SpinEvent = AtomicEvent(struct { + fn wake(ptr: *const u32) void {} + + fn wait(ptr: *const u32, expected: u32, timeout: ?u64) ResetEvent.WaitError!void { + // TODO: handle platforms where time.Timer.start() fails + var spin = Backoff.init(); + var timer = if (timeout == null) null else time.Timer.start() catch unreachable; + while (@atomicLoad(u32, ptr, .Acquire) == expected) { + spin.yield(); + if (timeout) |timeout_ns| { + if (timer.?.read() > timeout_ns) + return ResetEvent.WaitError.TimedOut; + } + } + } +}); + +const LinuxEvent = AtomicEvent(struct { + fn wake(ptr: *const u32) void { + const key = @ptrCast(*const i32, ptr); + const rc = linux.futex_wake(key, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1); + assert(linux.getErrno(rc) == 0); + } + + fn wait(ptr: *const u32, expected: u32, timeout: ?u64) ResetEvent.WaitError!void { + var ts: linux.timespec = undefined; + var ts_ptr: ?*linux.timespec = null; + if (timeout) |timeout_ns| { + ts_ptr = &ts; + ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s); + ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s); + } + + const key = @ptrCast(*const i32, ptr); + const key_expect = @bitCast(i32, expected); + while (@atomicLoad(i32, key, .Acquire) == key_expect) { + const rc = linux.futex_wait(key, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, key_expect, ts_ptr); + switch (linux.getErrno(rc)) { + 0, linux.EAGAIN => break, + linux.EINTR => continue, + linux.ETIMEDOUT => return ResetEvent.WaitError.TimedOut, + else => unreachable, + } + } + } +}); + +const WindowsEvent = AtomicEvent(struct { + fn wake(ptr: *const u32) void { + if (getEventHandle()) |handle| { + const key = @ptrCast(*const c_void, ptr); + const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); + assert(rc == 0); + } + } + + fn wait(ptr: *const u32, expected: u32, timeout: ?u64) ResetEvent.WaitError!void { + // fallback to spinlock if NT Keyed Events arent available + const handle = getEventHandle() orelse { + return SpinEvent.Futex.wait(ptr, expected, timeout); + }; + + // NT uses timeouts in units of 100ns with negative value being relative + var timeout_ptr: ?*windows.LARGE_INTEGER = null; + var timeout_value: windows.LARGE_INTEGER = undefined; + if (timeout) |timeout_ns| { + timeout_ptr = &timeout_value; + timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100); + } + + // NtWaitForKeyedEvent doesnt have spurious wake-ups + if (@atomicLoad(u32, ptr, .Acquire) == expected) { + const key = @ptrCast(*const c_void, ptr); + const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr); + switch (rc) { + 0 => {}, + windows.WAIT_TIMEOUT => return ResetEvent.WaitError.TimedOut, + else => unreachable, + } + } + } + + var keyed_state = State.Uninitialized; + var keyed_handle: ?windows.HANDLE = null; + + const State = enum(u8) { + Uninitialized, + Intializing, + Initialized, + }; + + fn getEventHandle() ?windows.HANDLE { + var spin = Backoff.init(); + var state = @atomicLoad(State, &keyed_state, .Monotonic); + + while (true) { + switch (state) { + .Initialized => { + return keyed_handle; + }, + .Intializing => { + spin.yield(); + state = @atomicLoad(State, &keyed_state, .Acquire); + }, + .Uninitialized => state = @cmpxchgWeak(State, &keyed_state, state, .Intializing, .Acquire, .Monotonic) orelse { + var handle: windows.HANDLE = undefined; + const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE; + if (windows.ntdll.NtCreateKeyedEvent(&handle, access_mask, null, 0) == 0) + keyed_handle = handle; + @atomicStore(State, &keyed_state, .Initialized, .Release); + return keyed_handle; + }, + } + } + } +}); + +const PosixEvent = struct { + state: u32, + cond: c.pthread_cond_t, + mutex: c.pthread_mutex_t, + + const IS_SET: u32 = 1; + + pub fn init() PosixEvent { + return PosixEvent{ + .state = .0, + .cond = c.PTHREAD_COND_INITIALIZER, + .mutex = c.PTHREAD_MUTEX_INITIALIZER, + }; + } + + pub fn deinit(self: *PosixEvent) void { + // On dragonfly, the destroy functions return EINVAL if they were initialized statically. + const retm = c.pthread_mutex_destroy(&self.mutex); + assert(retm == 0 or retm == (if (builtin.os == .dragonfly) os.EINVAL else 0)); + const retc = c.pthread_cond_destroy(&self.cond); + assert(retc == 0 or retc == (if (builtin.os == .dragonfly) os.EINVAL else 0)); + } + + pub fn isSet(self: *PosixEvent) bool { + assert(c.pthread_mutex_lock(&self.mutex) == 0); + defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); + + return self.state == IS_SET; + } + + pub fn reset(self: *PosixEvent) bool { + assert(c.pthread_mutex_lock(&self.mutex) == 0); + defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); + + const was_set = self.state == IS_SET; + self.state = 0; + return was_set; + } + + pub fn set(self: *PosixEvent, auto_reset: bool) bool { + assert(c.pthread_mutex_lock(&self.mutex) == 0); + defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); + + const had_waiter = self.state > IS_SET; + self.state = if (auto_reset) 0 else IS_SET; + if (had_waiter) { + assert(c.pthread_cond_signal(&self.cond) == 0); + } + return had_waiter; + } + + pub fn wait(self: *PosixEvent, timeout: ?u64) ResetEvent.WaitError!bool { + assert(c.pthread_mutex_lock(&self.mutex) == 0); + defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); + + if (self.state == IS_SET) + return false; + + var ts: os.timespec = undefined; + if (timeout) |timeout_ns| { + var timeout_abs = timeout_ns; + if (comptime std.Target.current.isDarwin()) { + var tv: os.darwin.timeval = undefined; + assert(os.darwin.gettimeofday(&tv, null) == 0); + timeout_abs += @intCast(u64, tv.tv_sec) * time.second; + timeout_abs += @intCast(u64, tv.tv_usec) * time.microsecond; + } else { + os.clock_gettime(os.CLOCK_REALTIME, &ts) catch unreachable; + timeout_abs += @intCast(u64, ts.tv_sec) * time.second; + timeout_abs += @intCast(u64, ts.tv_nsec); + } + ts.tv_sec = @intCast(@typeOf(ts.tv_sec), @divFloor(timeout_abs, time.second)); + ts.tv_nsec = @intCast(@typeOf(ts.tv_nsec), @mod(timeout_abs, time.second)); + } + + var dummy_value: u32 = undefined; + var wait_token = @truncate(u32, @ptrToInt(&dummy_value)); + self.state = wait_token; + + while (self.state == wait_token) { + const rc = switch (timeout == null) { + true => c.pthread_cond_wait(&self.cond, &self.mutex), + else => c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts), + }; + // TODO: rc appears to be the positive error code making os.errno() always return 0 on linux + switch (std.math.max(@as(c_int, os.errno(rc)), rc)) { + 0 => {}, + os.ETIMEDOUT => return ResetEvent.WaitError.TimedOut, + os.EINVAL => unreachable, + os.EPERM => unreachable, + else => unreachable, + } + } + return true; + } +}; + +test "std.ResetEvent" { + // TODO + if (builtin.single_threaded) + return error.SkipZigTest; + + var event = ResetEvent.init(); + defer event.deinit(); + + // test event setting + testing.expect(event.isSet() == false); + testing.expect(event.set(false) == false); + testing.expect(event.isSet() == true); + + // test event resetting + testing.expect(event.reset() == true); + testing.expect(event.isSet() == false); + testing.expect(event.reset() == false); + + // test cross thread signaling + const Context = struct { + event: ResetEvent, + value: u128, + + fn receiver(self: *@This()) void { + // wait for the sender to notify us with updated value + assert(self.value == 0); + assert((self.event.wait(1 * time.second) catch unreachable) == true); + assert(self.value == 1); + + // wait for sender to sleep, then notify it of new value + time.sleep(50 * time.millisecond); + self.value = 2; + assert(self.event.set(false) == true); + } + + fn sender(self: *@This()) !void { + // wait for the receiver() to start wait()'ing + time.sleep(50 * time.millisecond); + + // update value to 1 and notify the receiver() + assert(self.value == 0); + self.value = 1; + assert(self.event.set(true) == true); + + // wait for the receiver to update the value & notify us + assert((try self.event.wait(1 * time.second)) == true); + assert(self.value == 2); + } + }; + + _ = event.reset(); + var context = Context{ + .event = event, + .value = 0, + }; + + var receiver = try std.Thread.spawn(&context, Context.receiver); + defer receiver.wait(); + try context.sender(); +} \ No newline at end of file diff --git a/lib/std/special/c.zig b/lib/std/special/c.zig index 8ac00c4e04..c0eaf74a19 100644 --- a/lib/std/special/c.zig +++ b/lib/std/special/c.zig @@ -197,6 +197,49 @@ extern fn __stack_chk_fail() noreturn { // across .o file boundaries. fix comptime @ptrCast of nakedcc functions. nakedcc fn clone() void { switch (builtin.arch) { + .i386 => { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // +8, +12, +16, +20, +24, +28, +32 + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // eax, ebx, ecx, edx, esi, edi + asm volatile ( + \\ push %%ebp + \\ mov %%esp,%%ebp + \\ push %%ebx + \\ push %%esi + \\ push %%edi + \\ // Setup the arguments + \\ mov 16(%%ebp),%%ebx + \\ mov 12(%%ebp),%%ecx + \\ and $-16,%%ecx + \\ sub $20,%%ecx + \\ mov 20(%%ebp),%%eax + \\ mov %%eax,4(%%ecx) + \\ mov 8(%%ebp),%%eax + \\ mov %%eax,0(%%ecx) + \\ mov 24(%%ebp),%%edx + \\ mov 28(%%ebp),%%esi + \\ mov 32(%%ebp),%%edi + \\ mov $120,%%eax + \\ int $128 + \\ test %%eax,%%eax + \\ jnz 1f + \\ pop %%eax + \\ xor %%ebp,%%ebp + \\ call *%%eax + \\ mov %%eax,%%ebx + \\ xor %%eax,%%eax + \\ inc %%eax + \\ int $128 + \\ hlt + \\1: + \\ pop %%edi + \\ pop %%esi + \\ pop %%ebx + \\ pop %%ebp + \\ ret + ); + }, .x86_64 => { asm volatile ( \\ xor %%eax,%%eax diff --git a/lib/std/std.zig b/lib/std/std.zig index 83b7ed6e94..09db489604 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -16,6 +16,7 @@ pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; pub const Progress = @import("progress.zig").Progress; +pub const ResetEvent = @import("reset_event.zig").ResetEvent; pub const SegmentedList = @import("segmented_list.zig").SegmentedList; pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; pub const SpinLock = @import("spinlock.zig").SpinLock; @@ -23,7 +24,6 @@ pub const StringHashMap = @import("hash_map.zig").StringHashMap; pub const TailQueue = @import("linked_list.zig").TailQueue; pub const Target = @import("target.zig").Target; pub const Thread = @import("thread.zig").Thread; -pub const ThreadParker = @import("parker.zig").ThreadParker; pub const atomic = @import("atomic.zig"); pub const base64 = @import("base64.zig"); diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 09ab6e406d..2ed6c66fe6 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -89,7 +89,26 @@ pub fn expectEqual(expected: var, actual: @typeOf(expected)) void { if (union_info.tag_type == null) { @compileError("Unable to compare untagged union values"); } - @compileError("TODO implement testing.expectEqual for tagged unions"); + + const TagType = @TagType(@typeOf(expected)); + + const expectedTag = @as(TagType, expected); + const actualTag = @as(TagType, actual); + + expectEqual(expectedTag, actualTag); + + // we only reach this loop if the tags are equal + inline for (std.meta.fields(@typeOf(actual))) |fld| { + if (std.mem.eql(u8, fld.name, @tagName(actualTag))) { + expectEqual(@field(expected, fld.name), @field(actual, fld.name)); + return; + } + } + + // we iterate over *all* union fields + // => we should never get here as the loop above is + // including all possible values. + unreachable; }, .Optional => { @@ -124,6 +143,19 @@ pub fn expectEqual(expected: var, actual: @typeOf(expected)) void { } } +test "expectEqual.union(enum)" +{ + const T = union(enum) { + a: i32, + b: f32, + }; + + const a10 = T { .a = 10 }; + const a20 = T { .a = 20 }; + + expectEqual(a10, a10); +} + /// This function is intended to be used only in tests. When the two slices are not /// equal, prints diagnostics to stderr to show exactly how they are not equal, /// then aborts. diff --git a/lib/std/thread.zig b/lib/std/thread.zig index fe976a6839..571773dcae 100644 --- a/lib/std/thread.zig +++ b/lib/std/thread.zig @@ -314,11 +314,38 @@ pub const Thread = struct { os.CLONE_THREAD | os.CLONE_SYSVSEM | os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID | os.CLONE_DETACHED; var newtls: usize = undefined; + // This structure is only needed when targeting i386 + var user_desc: if (builtin.arch == .i386) os.linux.user_desc else void = undefined; + if (os.linux.tls.tls_image) |tls_img| { - newtls = os.linux.tls.copyTLS(mmap_addr + tls_start_offset); + if (builtin.arch == .i386) { + user_desc = os.linux.user_desc{ + .entry_number = tls_img.gdt_entry_number, + .base_addr = os.linux.tls.copyTLS(mmap_addr + tls_start_offset), + .limit = 0xfffff, + .seg_32bit = 1, + .contents = 0, // Data + .read_exec_only = 0, + .limit_in_pages = 1, + .seg_not_present = 0, + .useable = 1, + }; + newtls = @ptrToInt(&user_desc); + } else { + newtls = os.linux.tls.copyTLS(mmap_addr + tls_start_offset); + } flags |= os.CLONE_SETTLS; } - const rc = os.linux.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle); + + const rc = os.linux.clone( + MainFuncs.linuxThreadMain, + mmap_addr + stack_end_offset, + flags, + arg, + &thread_ptr.data.handle, + newtls, + &thread_ptr.data.handle, + ); switch (os.errno(rc)) { 0 => return thread_ptr, os.EAGAIN => return error.ThreadQuotaExceeded, diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 7d4fb5fa72..030112985a 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -25,10 +25,10 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) const context = llvm_handle.node.data; - const module = llvm.ModuleCreateWithNameInContext(comp.name.ptr(), context) orelse return error.OutOfMemory; + const module = llvm.ModuleCreateWithNameInContext(comp.name.toSliceConst(), context) orelse return error.OutOfMemory; defer llvm.DisposeModule(module); - llvm.SetTarget(module, comp.llvm_triple.ptr()); + llvm.SetTarget(module, comp.llvm_triple.toSliceConst()); llvm.SetDataLayout(module, comp.target_layout_str); if (util.getObjectFormat(comp.target) == .coff) { @@ -48,23 +48,23 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) const producer = try std.Buffer.allocPrint( &code.arena.allocator, "zig {}.{}.{}", - u32(c.ZIG_VERSION_MAJOR), - u32(c.ZIG_VERSION_MINOR), - u32(c.ZIG_VERSION_PATCH), + @as(u32, c.ZIG_VERSION_MAJOR), + @as(u32, c.ZIG_VERSION_MINOR), + @as(u32, c.ZIG_VERSION_PATCH), ); const flags = ""; const runtime_version = 0; const compile_unit_file = llvm.CreateFile( dibuilder, - comp.name.ptr(), - comp.root_package.root_src_dir.ptr(), + comp.name.toSliceConst(), + comp.root_package.root_src_dir.toSliceConst(), ) orelse return error.OutOfMemory; const is_optimized = comp.build_mode != .Debug; const compile_unit = llvm.CreateCompileUnit( dibuilder, DW.LANG_C99, compile_unit_file, - producer.ptr(), + producer.toSliceConst(), is_optimized, flags, runtime_version, @@ -99,7 +99,7 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) // verify the llvm module when safety is on if (std.debug.runtime_safety) { - var error_ptr: ?[*]u8 = null; + var error_ptr: ?[*:0]u8 = null; _ = llvm.VerifyModule(ofile.module, llvm.AbortProcessAction, &error_ptr); } @@ -108,12 +108,12 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) const is_small = comp.build_mode == .ReleaseSmall; const is_debug = comp.build_mode == .Debug; - var err_msg: [*]u8 = undefined; + var err_msg: [*:0]u8 = undefined; // TODO integrate this with evented I/O if (llvm.TargetMachineEmitToFile( comp.target_machine, module, - output_path.ptr(), + output_path.toSliceConst(), llvm.EmitBinary, &err_msg, is_debug, @@ -154,7 +154,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) const llvm_fn_type = try fn_val.base.typ.getLlvmType(ofile.arena, ofile.context); const llvm_fn = llvm.AddFunction( ofile.module, - fn_val.symbol_name.ptr(), + fn_val.symbol_name.toSliceConst(), llvm_fn_type, ) orelse return error.OutOfMemory; @@ -379,7 +379,7 @@ fn renderLoadUntyped( ptr: *llvm.Value, alignment: Type.Pointer.Align, vol: Type.Pointer.Vol, - name: [*]const u8, + name: [*:0]const u8, ) !*llvm.Value { const result = llvm.BuildLoad(ofile.builder, ptr, name) orelse return error.OutOfMemory; switch (vol) { @@ -390,7 +390,7 @@ fn renderLoadUntyped( return result; } -fn renderLoad(ofile: *ObjectFile, ptr: *llvm.Value, ptr_type: *Type.Pointer, name: [*]const u8) !*llvm.Value { +fn renderLoad(ofile: *ObjectFile, ptr: *llvm.Value, ptr_type: *Type.Pointer, name: [*:0]const u8) !*llvm.Value { return renderLoadUntyped(ofile, ptr, ptr_type.key.alignment, ptr_type.key.vol, name); } @@ -438,7 +438,7 @@ pub fn renderAlloca( ) !*llvm.Value { const llvm_var_type = try var_type.getLlvmType(ofile.arena, ofile.context); const name_with_null = try std.cstr.addNullByte(ofile.arena, name); - const result = llvm.BuildAlloca(ofile.builder, llvm_var_type, name_with_null.ptr) orelse return error.OutOfMemory; + const result = llvm.BuildAlloca(ofile.builder, llvm_var_type, @ptrCast([*:0]const u8, name_with_null.ptr)) orelse return error.OutOfMemory; llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm_var_type)); return result; } diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 183c28d5ed..a3e15f77d0 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -93,7 +93,7 @@ pub const ZigCompiler = struct { return LlvmHandle{ .node = node }; } - pub async fn getNativeLibC(self: *ZigCompiler) !*LibCInstallation { + pub fn getNativeLibC(self: *ZigCompiler) !*LibCInstallation { if (self.native_libc.start()) |ptr| return ptr; try self.native_libc.data.findNative(self.allocator); self.native_libc.resolve(); @@ -133,62 +133,62 @@ pub const Compilation = struct { zig_std_dir: []const u8, /// lazily created when we need it - tmp_dir: event.Future(BuildError![]u8), + tmp_dir: event.Future(BuildError![]u8) = event.Future(BuildError![]u8).init(), - version_major: u32, - version_minor: u32, - version_patch: u32, + version_major: u32 = 0, + version_minor: u32 = 0, + version_patch: u32 = 0, - linker_script: ?[]const u8, - out_h_path: ?[]const u8, + linker_script: ?[]const u8 = null, + out_h_path: ?[]const u8 = null, - is_test: bool, - each_lib_rpath: bool, - strip: bool, + is_test: bool = false, + each_lib_rpath: bool = false, + strip: bool = false, is_static: bool, - linker_rdynamic: bool, + linker_rdynamic: bool = false, - clang_argv: []const []const u8, - lib_dirs: []const []const u8, - rpath_list: []const []const u8, - assembly_files: []const []const u8, + clang_argv: []const []const u8 = [_][]const u8{}, + lib_dirs: []const []const u8 = [_][]const u8{}, + rpath_list: []const []const u8 = [_][]const u8{}, + assembly_files: []const []const u8 = [_][]const u8{}, /// paths that are explicitly provided by the user to link against - link_objects: []const []const u8, + link_objects: []const []const u8 = [_][]const u8{}, /// functions that have their own objects that we need to link /// it uses an optional pointer so that tombstone removals are possible - fn_link_set: event.Locked(FnLinkSet), + fn_link_set: event.Locked(FnLinkSet) = event.Locked(FnLinkSet).init(FnLinkSet.init()), pub const FnLinkSet = std.TailQueue(?*Value.Fn); - windows_subsystem_windows: bool, - windows_subsystem_console: bool, + windows_subsystem_windows: bool = false, + windows_subsystem_console: bool = false, link_libs_list: ArrayList(*LinkLib), - libc_link_lib: ?*LinkLib, + libc_link_lib: ?*LinkLib = null, - err_color: errmsg.Color, + err_color: errmsg.Color = .Auto, - verbose_tokenize: bool, - verbose_ast_tree: bool, - verbose_ast_fmt: bool, - verbose_cimport: bool, - verbose_ir: bool, - verbose_llvm_ir: bool, - verbose_link: bool, + verbose_tokenize: bool = false, + verbose_ast_tree: bool = false, + verbose_ast_fmt: bool = false, + verbose_cimport: bool = false, + verbose_ir: bool = false, + verbose_llvm_ir: bool = false, + verbose_link: bool = false, - darwin_frameworks: []const []const u8, - darwin_version_min: DarwinVersionMin, + darwin_frameworks: []const []const u8 = [_][]const u8{}, + darwin_version_min: DarwinVersionMin = .None, - test_filters: []const []const u8, - test_name_prefix: ?[]const u8, + test_filters: []const []const u8 = [_][]const u8{}, + test_name_prefix: ?[]const u8 = null, - emit_file_type: Emit, + emit_file_type: Emit = .Binary, kind: Kind, - link_out_file: ?[]const u8, + link_out_file: ?[]const u8 = null, events: *event.Channel(Event), exported_symbol_names: event.Locked(Decl.Table), @@ -213,7 +213,7 @@ pub const Compilation = struct { target_machine: *llvm.TargetMachine, target_data_ref: *llvm.TargetData, - target_layout_str: [*]u8, + target_layout_str: [*:0]u8, target_ptr_bits: u32, /// for allocating things which have the same lifetime as this Compilation @@ -222,16 +222,16 @@ pub const Compilation = struct { root_package: *Package, std_package: *Package, - override_libc: ?*LibCInstallation, + override_libc: ?*LibCInstallation = null, /// need to wait on this group before deinitializing deinit_group: event.Group(void), - // destroy_frame: @Frame(createAsync), - // main_loop_frame: @Frame(Compilation.mainLoop), - main_loop_future: event.Future(void), + destroy_frame: *@Frame(createAsync), + main_loop_frame: *@Frame(Compilation.mainLoop), + main_loop_future: event.Future(void) = event.Future(void).init(), - have_err_ret_tracing: bool, + have_err_ret_tracing: bool = false, /// not locked because it is read-only primitive_type_table: TypeTable, @@ -243,7 +243,9 @@ pub const Compilation = struct { c_int_types: [CInt.list.len]*Type.Int, - // fs_watch: *fs.Watch(*Scope.Root), + fs_watch: *fs.Watch(*Scope.Root), + + cancelled: bool = false, const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql); const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql); @@ -348,7 +350,9 @@ pub const Compilation = struct { zig_lib_dir: []const u8, ) !*Compilation { var optional_comp: ?*Compilation = null; - var frame = async createAsync( + var frame = try zig_compiler.allocator.create(@Frame(createAsync)); + errdefer zig_compiler.allocator.destroy(frame); + frame.* = async createAsync( &optional_comp, zig_compiler, name, @@ -359,11 +363,11 @@ pub const Compilation = struct { is_static, zig_lib_dir, ); + // TODO causes segfault + // return optional_comp orelse if (await frame) |_| unreachable else |err| err; if (optional_comp) |comp| { return comp; - } else { - if (await frame) |_| unreachable else |err| return err; - } + } else if (await frame) |_| unreachable else |err| return err; } async fn createAsync( @@ -389,50 +393,13 @@ pub const Compilation = struct { .build_mode = build_mode, .zig_lib_dir = zig_lib_dir, .zig_std_dir = undefined, - .tmp_dir = event.Future(BuildError![]u8).init(), - // .destroy_frame = @frame(), - // .main_loop_frame = undefined, - .main_loop_future = event.Future(void).init(), + .destroy_frame = @frame(), + .main_loop_frame = undefined, .name = undefined, .llvm_triple = undefined, - - .version_major = 0, - .version_minor = 0, - .version_patch = 0, - - .verbose_tokenize = false, - .verbose_ast_tree = false, - .verbose_ast_fmt = false, - .verbose_cimport = false, - .verbose_ir = false, - .verbose_llvm_ir = false, - .verbose_link = false, - - .linker_script = null, - .out_h_path = null, - .is_test = false, - .each_lib_rpath = false, - .strip = false, .is_static = is_static, - .linker_rdynamic = false, - .clang_argv = &[_][]const u8{}, - .lib_dirs = &[_][]const u8{}, - .rpath_list = &[_][]const u8{}, - .assembly_files = &[_][]const u8{}, - .link_objects = &[_][]const u8{}, - .fn_link_set = event.Locked(FnLinkSet).init(FnLinkSet.init()), - .windows_subsystem_windows = false, - .windows_subsystem_console = false, .link_libs_list = undefined, - .libc_link_lib = null, - .err_color = errmsg.Color.Auto, - .darwin_frameworks = &[_][]const u8{}, - .darwin_version_min = DarwinVersionMin.None, - .test_filters = &[_][]const u8{}, - .test_name_prefix = null, - .emit_file_type = Emit.Binary, - .link_out_file = null, .exported_symbol_names = event.Locked(Decl.Table).init(Decl.Table.init(allocator)), .prelink_group = event.Group(BuildError!void).init(allocator), .deinit_group = event.Group(void).init(allocator), @@ -462,11 +429,9 @@ pub const Compilation = struct { .root_package = undefined, .std_package = undefined, - .override_libc = null, - .have_err_ret_tracing = false, .primitive_type_table = undefined, - // .fs_watch = undefined, + .fs_watch = undefined, }; comp.link_libs_list = ArrayList(*LinkLib).init(comp.arena()); comp.primitive_type_table = TypeTable.init(comp.arena()); @@ -538,13 +503,16 @@ pub const Compilation = struct { comp.root_package = try Package.create(comp.arena(), ".", ""); } - // comp.fs_watch = try fs.Watch(*Scope.Root).create(16); - // defer comp.fs_watch.destroy(); + comp.fs_watch = try fs.Watch(*Scope.Root).init(allocator, 16); + defer comp.fs_watch.deinit(); try comp.initTypes(); defer comp.primitive_type_table.deinit(); - // comp.main_loop_frame = async comp.mainLoop(); + comp.main_loop_frame = try allocator.create(@Frame(mainLoop)); + defer allocator.destroy(comp.main_loop_frame); + + comp.main_loop_frame.* = async comp.mainLoop(); // Set this to indicate that initialization completed successfully. // from here on out we must not return an error. // This must occur before the first suspend/await. @@ -563,7 +531,7 @@ pub const Compilation = struct { } /// it does ref the result because it could be an arbitrary integer size - pub async fn getPrimitiveType(comp: *Compilation, name: []const u8) !?*Type { + pub fn getPrimitiveType(comp: *Compilation, name: []const u8) !?*Type { if (name.len >= 2) { switch (name[0]) { 'i', 'u' => blk: { @@ -757,8 +725,11 @@ pub const Compilation = struct { } pub fn destroy(self: *Compilation) void { - // await self.main_loop_frame; - // resume self.destroy_frame; + const allocator = self.gpa(); + self.cancelled = true; + await self.main_loop_frame; + resume self.destroy_frame; + allocator.destroy(self.destroy_frame); } fn start(self: *Compilation) void { @@ -771,7 +742,7 @@ pub const Compilation = struct { var build_result = self.initialCompile(); - while (true) { + while (!self.cancelled) { const link_result = if (build_result) blk: { break :blk self.maybeLink(); } else |err| err; @@ -799,47 +770,47 @@ pub const Compilation = struct { self.events.put(Event{ .Error = err }); } - // // First, get an item from the watch channel, waiting on the channel. - // var group = event.Group(BuildError!void).init(self.gpa()); - // { - // const ev = (self.fs_watch.channel.get()) catch |err| { - // build_result = err; - // continue; - // }; - // const root_scope = ev.data; - // group.call(rebuildFile, self, root_scope) catch |err| { - // build_result = err; - // continue; - // }; - // } - // // Next, get all the items from the channel that are buffered up. - // while (self.fs_watch.channel.getOrNull()) |ev_or_err| { - // if (ev_or_err) |ev| { - // const root_scope = ev.data; - // group.call(rebuildFile, self, root_scope) catch |err| { - // build_result = err; - // continue; - // }; - // } else |err| { - // build_result = err; - // continue; - // } - // } - // build_result = group.wait(); + // First, get an item from the watch channel, waiting on the channel. + var group = event.Group(BuildError!void).init(self.gpa()); + { + const ev = (self.fs_watch.channel.get()) catch |err| { + build_result = err; + continue; + }; + const root_scope = ev.data; + group.call(rebuildFile, self, root_scope) catch |err| { + build_result = err; + continue; + }; + } + // Next, get all the items from the channel that are buffered up. + while (self.fs_watch.channel.getOrNull()) |ev_or_err| { + if (ev_or_err) |ev| { + const root_scope = ev.data; + group.call(rebuildFile, self, root_scope) catch |err| { + build_result = err; + continue; + }; + } else |err| { + build_result = err; + continue; + } + } + build_result = group.wait(); } } - async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) !void { + async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void { const tree_scope = blk: { - const source_code = ""; - // const source_code = fs.readFile( - // root_scope.realpath, - // max_src_size, - // ) catch |err| { - // try self.addCompileErrorCli(root_scope.realpath, "unable to open: {}", @errorName(err)); - // return; - // }; - // errdefer self.gpa().free(source_code); + const source_code = fs.readFile( + self.gpa(), + root_scope.realpath, + max_src_size, + ) catch |err| { + try self.addCompileErrorCli(root_scope.realpath, "unable to open: {}", @errorName(err)); + return; + }; + errdefer self.gpa().free(source_code); const tree = try std.zig.parse(self.gpa(), source_code); errdefer { @@ -877,7 +848,7 @@ pub const Compilation = struct { try decl_group.wait(); } - async fn rebuildChangedDecls( + fn rebuildChangedDecls( self: *Compilation, group: *event.Group(BuildError!void), locked_table: *Decl.Table, @@ -966,7 +937,7 @@ pub const Compilation = struct { } } - async fn initialCompile(self: *Compilation) !void { + fn initialCompile(self: *Compilation) !void { if (self.root_src_path) |root_src_path| { const root_scope = blk: { // TODO async/await std.fs.realpath @@ -985,7 +956,7 @@ pub const Compilation = struct { } } - async fn maybeLink(self: *Compilation) !void { + fn maybeLink(self: *Compilation) !void { (self.prelink_group.wait()) catch |err| switch (err) { error.SemanticAnalysisFailed => {}, else => return err, @@ -1169,11 +1140,10 @@ pub const Compilation = struct { return link_lib; } - /// cancels itself so no need to await or cancel the promise. async fn startFindingNativeLibC(self: *Compilation) void { - std.event.Loop.instance.?.yield(); + event.Loop.startCpuBoundOperation(); // we don't care if it fails, we're just trying to kick off the future resolution - _ = (self.zig_compiler.getNativeLibC()) catch return; + _ = self.zig_compiler.getNativeLibC() catch return; } /// General Purpose Allocator. Must free when done. @@ -1188,7 +1158,7 @@ pub const Compilation = struct { /// If the temporary directory for this compilation has not been created, it creates it. /// Then it creates a random file name in that dir and returns it. - pub async fn createRandomOutputPath(self: *Compilation, suffix: []const u8) !Buffer { + pub fn createRandomOutputPath(self: *Compilation, suffix: []const u8) !Buffer { const tmp_dir = try self.getTmpDir(); const file_prefix = self.getRandomFileName(); @@ -1204,14 +1174,14 @@ pub const Compilation = struct { /// If the temporary directory for this Compilation has not been created, creates it. /// Then returns it. The directory is unique to this Compilation and cleaned up when /// the Compilation deinitializes. - async fn getTmpDir(self: *Compilation) ![]const u8 { + fn getTmpDir(self: *Compilation) ![]const u8 { if (self.tmp_dir.start()) |ptr| return ptr.*; self.tmp_dir.data = self.getTmpDirImpl(); self.tmp_dir.resolve(); return self.tmp_dir.data; } - async fn getTmpDirImpl(self: *Compilation) ![]u8 { + fn getTmpDirImpl(self: *Compilation) ![]u8 { const comp_dir_name = self.getRandomFileName(); const zig_dir_path = try getZigDir(self.gpa()); defer self.gpa().free(zig_dir_path); @@ -1221,7 +1191,7 @@ pub const Compilation = struct { return tmp_dir; } - async fn getRandomFileName(self: *Compilation) [12]u8 { + fn getRandomFileName(self: *Compilation) [12]u8 { // here we replace the standard +/ with -_ so that it can be used in a file name const b64_fs_encoder = std.base64.Base64Encoder.init( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", @@ -1247,20 +1217,23 @@ pub const Compilation = struct { } /// Returns a value which has been ref()'d once - async fn analyzeConstValue( + fn analyzeConstValue( comp: *Compilation, tree_scope: *Scope.AstTree, scope: *Scope, node: *ast.Node, expected_type: *Type, ) !*Value { - const analyzed_code = try comp.genAndAnalyzeCode(tree_scope, scope, node, expected_type); + var frame = try comp.gpa().create(@Frame(genAndAnalyzeCode)); + defer comp.gpa().destroy(frame); + frame.* = async comp.genAndAnalyzeCode(tree_scope, scope, node, expected_type); + const analyzed_code = try await frame; defer analyzed_code.destroy(comp.gpa()); return analyzed_code.getCompTimeResult(comp); } - async fn analyzeTypeExpr(comp: *Compilation, tree_scope: *Scope.AstTree, scope: *Scope, node: *ast.Node) !*Type { + fn analyzeTypeExpr(comp: *Compilation, tree_scope: *Scope.AstTree, scope: *Scope, node: *ast.Node) !*Type { const meta_type = &Type.MetaType.get(comp).base; defer meta_type.base.deref(comp); @@ -1291,7 +1264,7 @@ fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib } /// The function that actually does the generation. -async fn generateDecl(comp: *Compilation, decl: *Decl) !void { +fn generateDecl(comp: *Compilation, decl: *Decl) !void { switch (decl.id) { .Var => @panic("TODO"), .Fn => { @@ -1302,7 +1275,7 @@ async fn generateDecl(comp: *Compilation, decl: *Decl) !void { } } -async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { +fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { const tree_scope = fn_decl.base.tree_scope; const body_node = fn_decl.fn_proto.body_node orelse return generateDeclFnProto(comp, fn_decl); @@ -1319,7 +1292,7 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { // The Decl.Fn owns the initial 1 reference count const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name); - fn_decl.value = Decl.Fn.Val{ .Fn = fn_val }; + fn_decl.value = .{ .Fn = fn_val }; symbol_name_consumed = true; // Define local parameter variables @@ -1354,12 +1327,15 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { try fn_type.non_key.Normal.variable_list.append(var_scope); } - const analyzed_code = try comp.genAndAnalyzeCode( + var frame = try comp.gpa().create(@Frame(Compilation.genAndAnalyzeCode)); + defer comp.gpa().destroy(frame); + frame.* = async comp.genAndAnalyzeCode( tree_scope, fn_val.child_scope, body_node, fn_type.key.data.Normal.return_type, ); + const analyzed_code = try await frame; errdefer analyzed_code.destroy(comp.gpa()); assert(fn_val.block_scope != null); @@ -1386,7 +1362,7 @@ fn getZigDir(allocator: *mem.Allocator) ![]u8 { return std.fs.getAppDataDir(allocator, "zig"); } -async fn analyzeFnType( +fn analyzeFnType( comp: *Compilation, tree_scope: *Scope.AstTree, scope: *Scope, @@ -1448,7 +1424,7 @@ async fn analyzeFnType( return fn_type; } -async fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void { +fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void { const fn_type = try analyzeFnType( comp, fn_decl.base.tree_scope, @@ -1463,6 +1439,6 @@ async fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void { // The Decl.Fn owns the initial 1 reference count const fn_proto_val = try Value.FnProto.create(comp, fn_type, symbol_name); - fn_decl.value = Decl.Fn.Val{ .FnProto = fn_proto_val }; + fn_decl.value = .{ .FnProto = fn_proto_val }; symbol_name_consumed = true; } diff --git a/src-self-hosted/decl.zig b/src-self-hosted/decl.zig index 21a99729f5..e68a1458d6 100644 --- a/src-self-hosted/decl.zig +++ b/src-self-hosted/decl.zig @@ -69,15 +69,12 @@ pub const Decl = struct { pub const Fn = struct { base: Decl, - value: Val, - fn_proto: *ast.Node.FnProto, - - // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous - pub const Val = union(enum) { + value: union(enum) { Unresolved, Fn: *Value.Fn, FnProto: *Value.FnProto, - }; + }, + fn_proto: *ast.Node.FnProto, pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { return if (self.fn_proto.extern_export_inline_token) |tok_index| x: { diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 905d218114..8d1c32cefd 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -110,7 +110,7 @@ pub const Inst = struct { unreachable; } - pub async fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst { + pub fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst { switch (base.id) { .Return => return @fieldParentPtr(Return, "base", base).analyze(ira), .Const => return @fieldParentPtr(Const, "base", base).analyze(ira), @@ -422,7 +422,7 @@ pub const Inst = struct { return false; } - pub async fn analyze(self: *const Ref, ira: *Analyze) !*Inst { + pub fn analyze(self: *const Ref, ira: *Analyze) !*Inst { const target = try self.params.target.getAsParam(); if (ira.getCompTimeValOrNullUndefOk(target)) |val| { @@ -472,7 +472,7 @@ pub const Inst = struct { return false; } - pub async fn analyze(self: *const DeclRef, ira: *Analyze) !*Inst { + pub fn analyze(self: *const DeclRef, ira: *Analyze) !*Inst { (ira.irb.comp.resolveDecl(self.params.decl)) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.SemanticAnalysisFailed, @@ -516,7 +516,7 @@ pub const Inst = struct { return false; } - pub async fn analyze(self: *const VarPtr, ira: *Analyze) !*Inst { + pub fn analyze(self: *const VarPtr, ira: *Analyze) !*Inst { switch (self.params.var_scope.data) { .Const => @panic("TODO"), .Param => |param| { @@ -563,7 +563,7 @@ pub const Inst = struct { return false; } - pub async fn analyze(self: *const LoadPtr, ira: *Analyze) !*Inst { + pub fn analyze(self: *const LoadPtr, ira: *Analyze) !*Inst { const target = try self.params.target.getAsParam(); const target_type = target.getKnownType(); if (target_type.id != .Pointer) { @@ -645,7 +645,7 @@ pub const Inst = struct { return false; } - pub async fn analyze(self: *const PtrType, ira: *Analyze) !*Inst { + pub fn analyze(self: *const PtrType, ira: *Analyze) !*Inst { const child_type = try self.params.child_type.getAsConstType(ira); // if (child_type->id == TypeTableEntryIdUnreachable) { // ir_add_error(ira, &instruction->base, buf_sprintf("pointer to noreturn not allowed")); @@ -658,7 +658,7 @@ pub const Inst = struct { const amt = try align_inst.getAsConstAlign(ira); break :blk Type.Pointer.Align{ .Override = amt }; } else blk: { - break :blk Type.Pointer.Align{ .Abi = {} }; + break :blk .Abi; }; const ptr_type = try Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ .child_type = child_type, @@ -927,7 +927,7 @@ pub const Variable = struct { pub const BasicBlock = struct { ref_count: usize, - name_hint: [*]const u8, // must be a C string literal + name_hint: [*:0]const u8, debug_id: usize, scope: *Scope, instruction_list: std.ArrayList(*Inst), @@ -1051,7 +1051,7 @@ pub const Builder = struct { } /// No need to clean up resources thanks to the arena allocator. - pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: [*]const u8) !*BasicBlock { + pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: [*:0]const u8) !*BasicBlock { const basic_block = try self.arena().create(BasicBlock); basic_block.* = BasicBlock{ .ref_count = 0, @@ -1078,6 +1078,14 @@ pub const Builder = struct { self.current_basic_block = basic_block; } + pub fn genNodeRecursive(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst { + const alloc = irb.comp.gpa(); + var frame = try alloc.create(@Frame(genNode)); + defer alloc.destroy(frame); + frame.* = async irb.genNode(node, scope, lval); + return await frame; + } + pub async fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst { switch (node.id) { .Root => unreachable, @@ -1157,7 +1165,7 @@ pub const Builder = struct { }, .GroupedExpression => { const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node); - return irb.genNode(grouped_expr.expr, scope, lval); + return irb.genNodeRecursive(grouped_expr.expr, scope, lval); }, .BuiltinCall => return error.Unimplemented, .ErrorSetDecl => return error.Unimplemented, @@ -1186,14 +1194,14 @@ pub const Builder = struct { } } - async fn genCall(irb: *Builder, suffix_op: *ast.Node.SuffixOp, call: *ast.Node.SuffixOp.Op.Call, scope: *Scope) !*Inst { - const fn_ref = try irb.genNode(suffix_op.lhs, scope, .None); + fn genCall(irb: *Builder, suffix_op: *ast.Node.SuffixOp, call: *ast.Node.SuffixOp.Op.Call, scope: *Scope) !*Inst { + const fn_ref = try irb.genNodeRecursive(suffix_op.lhs.node, scope, .None); const args = try irb.arena().alloc(*Inst, call.params.len); var it = call.params.iterator(0); var i: usize = 0; while (it.next()) |arg_node_ptr| : (i += 1) { - args[i] = try irb.genNode(arg_node_ptr.*, scope, .None); + args[i] = try irb.genNodeRecursive(arg_node_ptr.*, scope, .None); } //bool is_async = node->data.fn_call_expr.is_async; @@ -1214,7 +1222,7 @@ pub const Builder = struct { //return ir_lval_wrap(irb, scope, fn_call, lval); } - async fn genPtrType( + fn genPtrType( irb: *Builder, prefix_op: *ast.Node.PrefixOp, ptr_info: ast.Node.PrefixOp.PtrInfo, @@ -1238,7 +1246,7 @@ pub const Builder = struct { //} else { // align_value = nullptr; //} - const child_type = try irb.genNode(prefix_op.rhs, scope, .None); + const child_type = try irb.genNodeRecursive(prefix_op.rhs, scope, .None); //uint32_t bit_offset_start = 0; //if (node->data.pointer_type.bit_offset_start != nullptr) { @@ -1307,9 +1315,9 @@ pub const Builder = struct { var rest: []const u8 = undefined; if (int_token.len >= 3 and int_token[0] == '0') { base = switch (int_token[1]) { - 'b' => u8(2), - 'o' => u8(8), - 'x' => u8(16), + 'b' => 2, + 'o' => 8, + 'x' => 16, else => unreachable, }; rest = int_token[2..]; @@ -1339,7 +1347,7 @@ pub const Builder = struct { return inst; } - pub async fn genStrLit(irb: *Builder, str_lit: *ast.Node.StringLiteral, scope: *Scope) !*Inst { + pub fn genStrLit(irb: *Builder, str_lit: *ast.Node.StringLiteral, scope: *Scope) !*Inst { const str_token = irb.code.tree_scope.tree.tokenSlice(str_lit.token); const src_span = Span.token(str_lit.token); @@ -1389,7 +1397,7 @@ pub const Builder = struct { } } - pub async fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst { + pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst { const block_scope = try Scope.Block.create(irb.comp, parent_scope); const outer_block_scope = &block_scope.base; @@ -1437,7 +1445,7 @@ pub const Builder = struct { child_scope = &defer_child_scope.base; continue; } - const statement_value = try irb.genNode(statement_node, child_scope, .None); + const statement_value = try irb.genNodeRecursive(statement_node, child_scope, .None); is_continuation_unreachable = statement_value.isNoReturn(); if (is_continuation_unreachable) { @@ -1499,7 +1507,7 @@ pub const Builder = struct { return irb.buildConstVoid(child_scope, Span.token(block.rbrace), true); } - pub async fn genControlFlowExpr( + pub fn genControlFlowExpr( irb: *Builder, control_flow_expr: *ast.Node.ControlFlowExpression, scope: *Scope, @@ -1533,7 +1541,7 @@ pub const Builder = struct { const outer_scope = irb.begin_scope.?; const return_value = if (control_flow_expr.rhs) |rhs| blk: { - break :blk try irb.genNode(rhs, scope, .None); + break :blk try irb.genNodeRecursive(rhs, scope, .None); } else blk: { break :blk try irb.buildConstVoid(scope, src_span, true); }; @@ -1596,7 +1604,7 @@ pub const Builder = struct { } } - pub async fn genIdentifier(irb: *Builder, identifier: *ast.Node.Identifier, scope: *Scope, lval: LVal) !*Inst { + pub fn genIdentifier(irb: *Builder, identifier: *ast.Node.Identifier, scope: *Scope, lval: LVal) !*Inst { const src_span = Span.token(identifier.token); const name = irb.code.tree_scope.tree.tokenSlice(identifier.token); @@ -1694,7 +1702,7 @@ pub const Builder = struct { return result; } - async fn genDefersForBlock( + fn genDefersForBlock( irb: *Builder, inner_scope: *Scope, outer_scope: *Scope, @@ -1712,7 +1720,7 @@ pub const Builder = struct { }; if (generate) { const defer_expr_scope = defer_scope.defer_expr_scope; - const instruction = try irb.genNode( + const instruction = try irb.genNodeRecursive( defer_expr_scope.expr_node, &defer_expr_scope.base, .None, @@ -1797,7 +1805,7 @@ pub const Builder = struct { // Look at the params and ref() other instructions comptime var i = 0; inline while (i < @memberCount(I.Params)) : (i += 1) { - const FieldType = comptime @typeOf(@field(I.Params(undefined), @memberName(I.Params, i))); + const FieldType = comptime @typeOf(@field(@as(I.Params, undefined), @memberName(I.Params, i))); switch (FieldType) { *Inst => @field(inst.params, @memberName(I.Params, i)).ref(self), *BasicBlock => @field(inst.params, @memberName(I.Params, i)).ref(self), @@ -1909,7 +1917,7 @@ pub const Builder = struct { VarScope: *Scope.Var, }; - async fn findIdent(irb: *Builder, scope: *Scope, name: []const u8) Ident { + fn findIdent(irb: *Builder, scope: *Scope, name: []const u8) Ident { var s = scope; while (true) { switch (s.id) { @@ -2519,7 +2527,7 @@ const Analyze = struct { } }; -pub async fn gen( +pub fn gen( comp: *Compilation, body_node: *ast.Node, tree_scope: *Scope.AstTree, @@ -2541,7 +2549,7 @@ pub async fn gen( return irb.finish(); } -pub async fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) !*Code { +pub fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) !*Code { const old_entry_bb = old_code.basic_block_list.at(0); var ira = try Analyze.init(comp, old_code.tree_scope, expected_type); diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig index b7a30dbb9f..b339a07c90 100644 --- a/src-self-hosted/libc_installation.zig +++ b/src-self-hosted/libc_installation.zig @@ -143,7 +143,7 @@ pub const LibCInstallation = struct { } /// Finds the default, native libc. - pub async fn findNative(self: *LibCInstallation, allocator: *Allocator) !void { + pub fn findNative(self: *LibCInstallation, allocator: *Allocator) !void { self.initEmpty(); var group = event.Group(FindError!void).init(allocator); errdefer group.wait() catch {}; @@ -393,14 +393,14 @@ pub const LibCInstallation = struct { }; /// caller owns returned memory -async fn ccPrintFileName(allocator: *Allocator, o_file: []const u8, want_dirname: bool) ![]u8 { +fn ccPrintFileName(allocator: *Allocator, o_file: []const u8, want_dirname: bool) ![]u8 { const cc_exe = std.os.getenv("CC") orelse "cc"; const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={}", o_file); defer allocator.free(arg1); const argv = [_][]const u8{ cc_exe, arg1 }; // TODO This simulates evented I/O for the child process exec - std.event.Loop.instance.?.yield(); + event.Loop.startCpuBoundOperation(); const errorable_result = std.ChildProcess.exec(allocator, &argv, null, null, 1024 * 1024); const exec_result = if (std.debug.runtime_safety) blk: { break :blk errorable_result catch unreachable; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index aafe05eb8b..67490b4e1f 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -11,7 +11,7 @@ const util = @import("util.zig"); const Context = struct { comp: *Compilation, arena: std.heap.ArenaAllocator, - args: std.ArrayList([*]const u8), + args: std.ArrayList([*:0]const u8), link_in_crt: bool, link_err: error{OutOfMemory}!void, @@ -21,7 +21,7 @@ const Context = struct { out_file_path: std.Buffer, }; -pub async fn link(comp: *Compilation) !void { +pub fn link(comp: *Compilation) !void { var ctx = Context{ .comp = comp, .arena = std.heap.ArenaAllocator.init(comp.gpa()), @@ -33,7 +33,7 @@ pub async fn link(comp: *Compilation) !void { .out_file_path = undefined, }; defer ctx.arena.deinit(); - ctx.args = std.ArrayList([*]const u8).init(&ctx.arena.allocator); + ctx.args = std.ArrayList([*:0]const u8).init(&ctx.arena.allocator); ctx.link_msg = std.Buffer.initNull(&ctx.arena.allocator); if (comp.link_out_file) |out_file| { @@ -58,7 +58,8 @@ pub async fn link(comp: *Compilation) !void { try ctx.args.append("lld"); if (comp.haveLibC()) { - ctx.libc = ctx.comp.override_libc orelse blk: { + // TODO https://github.com/ziglang/zig/issues/3190 + var libc = ctx.comp.override_libc orelse blk: { switch (comp.target) { Target.Native => { break :blk comp.zig_compiler.getNativeLibC() catch return error.LibCRequiredButNotProvidedOrFound; @@ -66,6 +67,7 @@ pub async fn link(comp: *Compilation) !void { else => return error.LibCRequiredButNotProvidedOrFound, } }; + ctx.libc = libc; } try constructLinkerArgs(&ctx); @@ -171,7 +173,7 @@ fn constructLinkerArgsElf(ctx: *Context) !void { //} try ctx.args.append("-o"); - try ctx.args.append(ctx.out_file_path.ptr()); + try ctx.args.append(ctx.out_file_path.toSliceConst()); if (ctx.link_in_crt) { const crt1o = if (ctx.comp.is_static) "crt1.o" else "Scrt1.o"; @@ -214,10 +216,11 @@ fn constructLinkerArgsElf(ctx: *Context) !void { if (ctx.comp.haveLibC()) { try ctx.args.append("-L"); - try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.lib_dir.?)).ptr); + // TODO addNullByte should probably return [:0]u8 + try ctx.args.append(@ptrCast([*:0]const u8, (try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.lib_dir.?)).ptr)); try ctx.args.append("-L"); - try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.static_lib_dir.?)).ptr); + try ctx.args.append(@ptrCast([*:0]const u8, (try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.static_lib_dir.?)).ptr)); if (!ctx.comp.is_static) { const dl = blk: { @@ -226,7 +229,7 @@ fn constructLinkerArgsElf(ctx: *Context) !void { return error.LibCMissingDynamicLinker; }; try ctx.args.append("-dynamic-linker"); - try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, dl)).ptr); + try ctx.args.append(@ptrCast([*:0]const u8, (try std.cstr.addNullByte(&ctx.arena.allocator, dl)).ptr)); } } @@ -238,7 +241,7 @@ fn constructLinkerArgsElf(ctx: *Context) !void { // .o files for (ctx.comp.link_objects) |link_object| { const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); - try ctx.args.append(link_obj_with_null.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, link_obj_with_null.ptr)); } try addFnObjects(ctx); @@ -313,7 +316,7 @@ fn constructLinkerArgsElf(ctx: *Context) !void { fn addPathJoin(ctx: *Context, dirname: []const u8, basename: []const u8) !void { const full_path = try std.fs.path.join(&ctx.arena.allocator, [_][]const u8{ dirname, basename }); const full_path_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, full_path); - try ctx.args.append(full_path_with_null.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, full_path_with_null.ptr)); } fn constructLinkerArgsCoff(ctx: *Context) !void { @@ -339,12 +342,12 @@ fn constructLinkerArgsCoff(ctx: *Context) !void { const is_library = ctx.comp.kind == .Lib; const out_arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-OUT:{}\x00", ctx.out_file_path.toSliceConst()); - try ctx.args.append(out_arg.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, out_arg.ptr)); if (ctx.comp.haveLibC()) { - try ctx.args.append((try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.msvc_lib_dir.?)).ptr); - try ctx.args.append((try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.kernel32_lib_dir.?)).ptr); - try ctx.args.append((try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.lib_dir.?)).ptr); + try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.msvc_lib_dir.?)).ptr)); + try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.kernel32_lib_dir.?)).ptr)); + try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.lib_dir.?)).ptr)); } if (ctx.link_in_crt) { @@ -353,17 +356,17 @@ fn constructLinkerArgsCoff(ctx: *Context) !void { if (ctx.comp.is_static) { const cmt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "libcmt{}.lib\x00", d_str); - try ctx.args.append(cmt_lib_name.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, cmt_lib_name.ptr)); } else { const msvcrt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "msvcrt{}.lib\x00", d_str); - try ctx.args.append(msvcrt_lib_name.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, msvcrt_lib_name.ptr)); } const vcruntime_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}vcruntime{}.lib\x00", lib_str, d_str); - try ctx.args.append(vcruntime_lib_name.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, vcruntime_lib_name.ptr)); const crt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}ucrt{}.lib\x00", lib_str, d_str); - try ctx.args.append(crt_lib_name.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, crt_lib_name.ptr)); // Visual C++ 2015 Conformance Changes // https://msdn.microsoft.com/en-us/library/bb531344.aspx @@ -395,7 +398,7 @@ fn constructLinkerArgsCoff(ctx: *Context) !void { for (ctx.comp.link_objects) |link_object| { const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); - try ctx.args.append(link_obj_with_null.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, link_obj_with_null.ptr)); } try addFnObjects(ctx); @@ -504,11 +507,7 @@ fn constructLinkerArgsMachO(ctx: *Context) !void { //} try ctx.args.append("-arch"); - const darwin_arch_str = try std.cstr.addNullByte( - &ctx.arena.allocator, - ctx.comp.target.getDarwinArchString(), - ); - try ctx.args.append(darwin_arch_str.ptr); + try ctx.args.append(util.getDarwinArchString(ctx.comp.target)); const platform = try DarwinPlatform.get(ctx.comp); switch (platform.kind) { @@ -517,7 +516,7 @@ fn constructLinkerArgsMachO(ctx: *Context) !void { .IPhoneOSSimulator => try ctx.args.append("-ios_simulator_version_min"), } const ver_str = try std.fmt.allocPrint(&ctx.arena.allocator, "{}.{}.{}\x00", platform.major, platform.minor, platform.micro); - try ctx.args.append(ver_str.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, ver_str.ptr)); if (ctx.comp.kind == .Exe) { if (ctx.comp.is_static) { @@ -528,7 +527,7 @@ fn constructLinkerArgsMachO(ctx: *Context) !void { } try ctx.args.append("-o"); - try ctx.args.append(ctx.out_file_path.ptr()); + try ctx.args.append(ctx.out_file_path.toSliceConst()); //for (size_t i = 0; i < g->rpath_list.length; i += 1) { // Buf *rpath = g->rpath_list.at(i); @@ -572,7 +571,7 @@ fn constructLinkerArgsMachO(ctx: *Context) !void { for (ctx.comp.link_objects) |link_object| { const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); - try ctx.args.append(link_obj_with_null.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, link_obj_with_null.ptr)); } try addFnObjects(ctx); @@ -593,10 +592,10 @@ fn constructLinkerArgsMachO(ctx: *Context) !void { } else { if (mem.indexOfScalar(u8, lib.name, '/') == null) { const arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-l{}\x00", lib.name); - try ctx.args.append(arg.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, arg.ptr)); } else { const arg = try std.cstr.addNullByte(&ctx.arena.allocator, lib.name); - try ctx.args.append(arg.ptr); + try ctx.args.append(@ptrCast([*:0]const u8, arg.ptr)); } } } @@ -626,20 +625,19 @@ fn constructLinkerArgsWasm(ctx: *Context) void { } fn addFnObjects(ctx: *Context) !void { - // at this point it's guaranteed nobody else has this lock, so we circumvent it - // and avoid having to be an async function - const fn_link_set = &ctx.comp.fn_link_set.private_data; + const held = ctx.comp.fn_link_set.acquire(); + defer held.release(); - var it = fn_link_set.first; + var it = held.value.first; while (it) |node| { const fn_val = node.data orelse { // handle the tombstone. See Value.Fn.destroy. it = node.next; - fn_link_set.remove(node); + held.value.remove(node); ctx.comp.gpa().destroy(node); continue; }; - try ctx.args.append(fn_val.containing_object.ptr()); + try ctx.args.append(fn_val.containing_object.toSliceConst()); it = node.next; } } diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig index af42adb97d..476637b2c2 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -86,7 +86,7 @@ pub const AddGlobal = LLVMAddGlobal; extern fn LLVMAddGlobal(M: *Module, Ty: *Type, Name: [*:0]const u8) ?*Value; pub const ConstStringInContext = LLVMConstStringInContext; -extern fn LLVMConstStringInContext(C: *Context, Str: [*:0]const u8, Length: c_uint, DontNullTerminate: Bool) ?*Value; +extern fn LLVMConstStringInContext(C: *Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: Bool) ?*Value; pub const ConstInt = LLVMConstInt; extern fn LLVMConstInt(IntTy: *Type, N: c_ulonglong, SignExtend: Bool) ?*Value; diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index b934ad8ee5..74e1ee9e7f 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -49,14 +49,15 @@ const usage = const Command = struct { name: []const u8, - exec: fn (*Allocator, []const []const u8) anyerror!void, + exec: async fn (*Allocator, []const []const u8) anyerror!void, }; pub fn main() !void { // This allocator needs to be thread-safe because we use it for the event.Loop // which multiplexes async functions onto kernel threads. // libc allocator is guaranteed to have this property. - const allocator = std.heap.c_allocator; + // TODO https://github.com/ziglang/zig/issues/3783 + const allocator = std.heap.page_allocator; stdout = &std.io.getStdOut().outStream().stream; @@ -118,14 +119,18 @@ pub fn main() !void { }, }; - for (commands) |command| { + inline for (commands) |command| { if (mem.eql(u8, command.name, args[1])) { - return command.exec(allocator, args[2..]); + var frame = try allocator.create(@Frame(command.exec)); + defer allocator.destroy(frame); + frame.* = async command.exec(allocator, args[2..]); + return await frame; } } try stderr.print("unknown command: {}\n\n", args[1]); try stderr.write(usage); + process.argsFree(allocator, args); process.exit(1); } @@ -461,13 +466,12 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co comp.link_objects = link_objects; comp.start(); - const frame = async processBuildEvents(comp, color); + processBuildEvents(comp, color); } -async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { +fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { var count: usize = 0; - while (true) { - // TODO directly awaiting async should guarantee memory allocation elision + while (!comp.cancelled) { const build_event = comp.events.get(); count += 1; @@ -545,7 +549,7 @@ fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_fil "Try running `zig libc` to see an example for the native target.\n", libc_paths_file, @errorName(err), - ) catch process.exit(1); + ) catch {}; process.exit(1); }; } @@ -567,12 +571,8 @@ fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void { var zig_compiler = try ZigCompiler.init(allocator); defer zig_compiler.deinit(); - const frame = async findLibCAsync(&zig_compiler); -} - -async fn findLibCAsync(zig_compiler: *ZigCompiler) void { const libc = zig_compiler.getNativeLibC() catch |err| { - stderr.print("unable to find libc: {}\n", @errorName(err)) catch process.exit(1); + stderr.print("unable to find libc: {}\n", @errorName(err)) catch {}; process.exit(1); }; libc.render(stdout) catch process.exit(1); @@ -644,11 +644,23 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { process.exit(1); } - return asyncFmtMain( - allocator, - &flags, - color, - ); + var fmt = Fmt{ + .allocator = allocator, + .seen = event.Locked(Fmt.SeenMap).init(Fmt.SeenMap.init(allocator)), + .any_error = false, + .color = color, + }; + + const check_mode = flags.present("check"); + + var group = event.Group(FmtError!void).init(allocator); + for (flags.positionals.toSliceConst()) |file_path| { + try group.call(fmtPath, &fmt, file_path, check_mode); + } + try group.wait(); + if (fmt.any_error) { + process.exit(1); + } } const FmtError = error{ @@ -673,30 +685,6 @@ const FmtError = error{ CurrentWorkingDirectoryUnlinked, } || fs.File.OpenError; -async fn asyncFmtMain( - allocator: *Allocator, - flags: *const Args, - color: errmsg.Color, -) FmtError!void { - var fmt = Fmt{ - .allocator = allocator, - .seen = event.Locked(Fmt.SeenMap).init(Fmt.SeenMap.init(allocator)), - .any_error = false, - .color = color, - }; - - const check_mode = flags.present("check"); - - var group = event.Group(FmtError!void).init(allocator); - for (flags.positionals.toSliceConst()) |file_path| { - try group.call(fmtPath, &fmt, file_path, check_mode); - } - try group.wait(); - if (fmt.any_error) { - process.exit(1); - } -} - async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void { const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); defer fmt.allocator.free(file_path); @@ -708,33 +696,34 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro if (try held.value.put(file_path, {})) |_| return; } - const source_code = ""; - // const source_code = event.fs.readFile( - // file_path, - // max_src_size, - // ) catch |err| switch (err) { - // error.IsDir, error.AccessDenied => { - // // TODO make event based (and dir.next()) - // var dir = try fs.Dir.cwd().openDirList(file_path); - // defer dir.close(); + const source_code = event.fs.readFile( + fmt.allocator, + file_path, + max_src_size, + ) catch |err| switch (err) { + error.IsDir, error.AccessDenied => { + var dir = try fs.cwd().openDirList(file_path); + defer dir.close(); - // var group = event.Group(FmtError!void).init(fmt.allocator); - // while (try dir.next()) |entry| { - // if (entry.kind == fs.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) { - // const full_path = try fs.path.join(fmt.allocator, [_][]const u8{ file_path, entry.name }); - // try group.call(fmtPath, fmt, full_path, check_mode); - // } - // } - // return group.wait(); - // }, - // else => { - // // TODO lock stderr printing - // try stderr.print("unable to open '{}': {}\n", file_path, err); - // fmt.any_error = true; - // return; - // }, - // }; - // defer fmt.allocator.free(source_code); + var group = event.Group(FmtError!void).init(fmt.allocator); + var it = dir.iterate(); + while (try it.next()) |entry| { + if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) { + const full_path = try fs.path.join(fmt.allocator, [_][]const u8{ file_path, entry.name }); + @panic("TODO https://github.com/ziglang/zig/issues/3777"); + // try group.call(fmtPath, fmt, full_path, check_mode); + } + } + return group.wait(); + }, + else => { + // TODO lock stderr printing + try stderr.print("unable to open '{}': {}\n", file_path, err); + fmt.any_error = true; + return; + }, + }; + defer fmt.allocator.free(source_code); const tree = std.zig.parse(fmt.allocator, source_code) catch |err| { try stderr.print("error parsing file '{}': {}\n", file_path, err); @@ -867,10 +856,12 @@ fn cmdInternal(allocator: *Allocator, args: []const []const u8) !void { .exec = cmdInternalBuildInfo, }}; - for (sub_commands) |sub_command| { + inline for (sub_commands) |sub_command| { if (mem.eql(u8, sub_command.name, args[0])) { - try sub_command.exec(allocator, args[1..]); - return; + var frame = try allocator.create(@Frame(sub_command.exec)); + defer allocator.destroy(frame); + frame.* = async sub_command.exec(allocator, args[1..]); + return await frame; } } diff --git a/src-self-hosted/stage1.zig b/src-self-hosted/stage1.zig index f8caaf3da2..a0fd689a36 100644 --- a/src-self-hosted/stage1.zig +++ b/src-self-hosted/stage1.zig @@ -279,7 +279,7 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) { error.IsDir, error.AccessDenied => { // TODO make event based (and dir.next()) - var dir = try fs.Dir.cwd().openDirList(file_path); + var dir = try fs.cwd().openDirList(file_path); defer dir.close(); var dir_it = dir.iterate(); @@ -427,11 +427,11 @@ export fn stage2_DepTokenizer_next(self: *stage2_DepTokenizer) stage2_DepNextRes }; } -export const stage2_DepTokenizer = extern struct { +const stage2_DepTokenizer = extern struct { handle: *DepTokenizer, }; -export const stage2_DepNextResult = extern struct { +const stage2_DepNextResult = extern struct { type_id: TypeId, // when type_id == error --> error text @@ -440,7 +440,7 @@ export const stage2_DepNextResult = extern struct { // when type_id == prereq --> prereq pathname textz: [*]const u8, - export const TypeId = extern enum { + const TypeId = extern enum { error_, null_, target, diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 85b9d6b912..62b7914dbc 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -26,7 +26,8 @@ test "stage2" { } const file1 = "1.zig"; -const allocator = std.heap.c_allocator; +// TODO https://github.com/ziglang/zig/issues/3783 +const allocator = std.heap.page_allocator; pub const TestContext = struct { zig_compiler: ZigCompiler, @@ -94,8 +95,8 @@ pub const TestContext = struct { &self.zig_compiler, "test", file1_path, - Target.Native, - Compilation.Kind.Obj, + .Native, + .Obj, .Debug, true, // is_static self.zig_lib_dir, @@ -116,7 +117,7 @@ pub const TestContext = struct { const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.incr()); const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 }); - const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", file1_path, (Target{.Native = {}}).exeFileExt()); + const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", file1_path, (Target{ .Native = {} }).exeFileExt()); if (std.fs.path.dirname(file1_path)) |dirname| { try std.fs.makePath(allocator, dirname); } @@ -128,8 +129,8 @@ pub const TestContext = struct { &self.zig_compiler, "test", file1_path, - Target.Native, - Compilation.Kind.Exe, + .Native, + .Exe, .Debug, false, self.zig_lib_dir, @@ -148,15 +149,12 @@ pub const TestContext = struct { exe_file: []const u8, expected_output: []const u8, ) anyerror!void { - // TODO this should not be necessary - const exe_file_2 = try std.mem.dupe(allocator, u8, exe_file); - defer comp.destroy(); const build_event = comp.events.get(); switch (build_event) { .Ok => { - const argv = [_][]const u8{exe_file_2}; + const argv = [_][]const u8{exe_file}; // TODO use event loop const child = try std.ChildProcess.exec(allocator, argv, null, null, 1024 * 1024); switch (child.term) { @@ -173,13 +171,13 @@ pub const TestContext = struct { return error.OutputMismatch; } }, - Compilation.Event.Error => |err| return err, - Compilation.Event.Fail => |msgs| { + .Error => @panic("Cannot return error: https://github.com/ziglang/zig/issues/3190"), // |err| return err, + .Fail => |msgs| { const stderr = std.io.getStdErr(); try stderr.write("build incorrectly failed:\n"); for (msgs) |msg| { defer msg.destroy(); - try msg.printToFile(stderr, errmsg.Color.Auto); + try msg.printToFile(stderr, .Auto); } }, } diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 3bf39d9085..8b068150b6 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -53,7 +53,7 @@ pub const Type = struct { base: *Type, allocator: *Allocator, llvm_context: *llvm.Context, - ) (error{OutOfMemory}!*llvm.Type) { + ) error{OutOfMemory}!*llvm.Type { switch (base.id) { .Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(allocator, llvm_context), .Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(allocator, llvm_context), @@ -184,7 +184,7 @@ pub const Type = struct { /// If you happen to have an llvm context handy, use getAbiAlignmentInContext instead. /// Otherwise, this one will grab one from the pool and then release it. - pub async fn getAbiAlignment(base: *Type, comp: *Compilation) !u32 { + pub fn getAbiAlignment(base: *Type, comp: *Compilation) !u32 { if (base.abi_alignment.start()) |ptr| return ptr.*; { @@ -200,7 +200,7 @@ pub const Type = struct { } /// If you have an llvm conext handy, you can use it here. - pub async fn getAbiAlignmentInContext(base: *Type, comp: *Compilation, llvm_context: *llvm.Context) !u32 { + pub fn getAbiAlignmentInContext(base: *Type, comp: *Compilation, llvm_context: *llvm.Context) !u32 { if (base.abi_alignment.start()) |ptr| return ptr.*; base.abi_alignment.data = base.resolveAbiAlignment(comp, llvm_context); @@ -209,7 +209,7 @@ pub const Type = struct { } /// Lower level function that does the work. See getAbiAlignment. - async fn resolveAbiAlignment(base: *Type, comp: *Compilation, llvm_context: *llvm.Context) !u32 { + fn resolveAbiAlignment(base: *Type, comp: *Compilation, llvm_context: *llvm.Context) !u32 { const llvm_type = try base.getLlvmType(comp.gpa(), llvm_context); return @intCast(u32, llvm.ABIAlignmentOfType(comp.target_data_ref, llvm_type)); } @@ -367,7 +367,7 @@ pub const Type = struct { } /// takes ownership of key.Normal.params on success - pub async fn get(comp: *Compilation, key: Key) !*Fn { + pub fn get(comp: *Compilation, key: Key) !*Fn { { const held = comp.fn_type_table.acquire(); defer held.release(); @@ -564,7 +564,7 @@ pub const Type = struct { return comp.u8_type; } - pub async fn get(comp: *Compilation, key: Key) !*Int { + pub fn get(comp: *Compilation, key: Key) !*Int { { const held = comp.int_type_table.acquire(); defer held.release(); @@ -606,7 +606,7 @@ pub const Type = struct { comp.registerGarbage(Int, &self.garbage_node); } - pub async fn gcDestroy(self: *Int, comp: *Compilation) void { + pub fn gcDestroy(self: *Int, comp: *Compilation) void { { const held = comp.int_type_table.acquire(); defer held.release(); @@ -700,7 +700,7 @@ pub const Type = struct { comp.registerGarbage(Pointer, &self.garbage_node); } - pub async fn gcDestroy(self: *Pointer, comp: *Compilation) void { + pub fn gcDestroy(self: *Pointer, comp: *Compilation) void { { const held = comp.ptr_type_table.acquire(); defer held.release(); @@ -711,14 +711,14 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub async fn getAlignAsInt(self: *Pointer, comp: *Compilation) u32 { + pub fn getAlignAsInt(self: *Pointer, comp: *Compilation) u32 { switch (self.key.alignment) { .Abi => return self.key.child_type.getAbiAlignment(comp), .Override => |alignment| return alignment, } } - pub async fn get( + pub fn get( comp: *Compilation, key: Key, ) !*Pointer { @@ -726,8 +726,10 @@ pub const Type = struct { switch (key.alignment) { .Abi => {}, .Override => |alignment| { + // TODO https://github.com/ziglang/zig/issues/3190 + var align_spill = alignment; const abi_align = try key.child_type.getAbiAlignment(comp); - if (abi_align == alignment) { + if (abi_align == align_spill) { normal_key.alignment = .Abi; } }, @@ -828,7 +830,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub async fn get(comp: *Compilation, key: Key) !*Array { + pub fn get(comp: *Compilation, key: Key) !*Array { key.elem_type.base.ref(); errdefer key.elem_type.base.deref(comp); diff --git a/src-self-hosted/util.zig b/src-self-hosted/util.zig index f1d892411f..070ba9ba87 100644 --- a/src-self-hosted/util.zig +++ b/src-self-hosted/util.zig @@ -32,21 +32,21 @@ pub fn getFloatAbi(self: Target) FloatAbi { }; } -pub fn getObjectFormat(self: Target) Target.ObjectFormat { - return switch (self) { - .Native => @import("builtin").object_format, - .Cross => { +pub fn getObjectFormat(target: Target) Target.ObjectFormat { + switch (target) { + .Native => return @import("builtin").object_format, + .Cross => blk: { if (target.isWindows() or target.isUefi()) { - break .coff; + return .coff; } else if (target.isDarwin()) { - break .macho; + return .macho; } if (target.isWasm()) { - break .wasm; + return .wasm; } - break .elf; + return .elf; }, - }; + } } pub fn getDynamicLinkerPath(self: Target) ?[]const u8 { @@ -156,7 +156,7 @@ pub fn getDynamicLinkerPath(self: Target) ?[]const u8 { } } -pub fn getDarwinArchString(self: Target) []const u8 { +pub fn getDarwinArchString(self: Target) [:0]const u8 { const arch = self.getArch(); switch (arch) { .aarch64 => return "arm64", @@ -166,7 +166,8 @@ pub fn getDarwinArchString(self: Target) []const u8 { .powerpc => return "ppc", .powerpc64 => return "ppc64", .powerpc64le => return "ppc64le", - else => return @tagName(arch), + // @tagName should be able to return sentinel terminated slice + else => @panic("TODO https://github.com/ziglang/zig/issues/3779"), //return @tagName(arch), } } diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 0458bac12a..accd70d9cc 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -156,7 +156,7 @@ pub const Value = struct { const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context); const llvm_fn = llvm.AddFunction( ofile.module, - self.symbol_name.ptr(), + self.symbol_name.toSliceConst(), llvm_fn_type, ) orelse return error.OutOfMemory; @@ -241,7 +241,7 @@ pub const Value = struct { const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context); const llvm_fn = llvm.AddFunction( ofile.module, - self.symbol_name.ptr(), + self.symbol_name.toSliceConst(), llvm_fn_type, ) orelse return error.OutOfMemory; @@ -334,7 +334,7 @@ pub const Value = struct { field_index: usize, }; - pub async fn createArrayElemPtr( + pub fn createArrayElemPtr( comp: *Compilation, array_val: *Array, mut: Type.Pointer.Mut, @@ -350,7 +350,7 @@ pub const Value = struct { .mut = mut, .vol = Type.Pointer.Vol.Non, .size = size, - .alignment = Type.Pointer.Align.Abi, + .alignment = .Abi, }); var ptr_type_consumed = false; errdefer if (!ptr_type_consumed) ptr_type.base.base.deref(comp); @@ -390,13 +390,13 @@ pub const Value = struct { const array_llvm_value = (try base_array.val.getLlvmConst(ofile)).?; const ptr_bit_count = ofile.comp.target_ptr_bits; const usize_llvm_type = llvm.IntTypeInContext(ofile.context, ptr_bit_count) orelse return error.OutOfMemory; - const indices = [_]*llvm.Value{ + var indices = [_]*llvm.Value{ llvm.ConstNull(usize_llvm_type) orelse return error.OutOfMemory, llvm.ConstInt(usize_llvm_type, base_array.elem_index, 0) orelse return error.OutOfMemory, }; return llvm.ConstInBoundsGEP( array_llvm_value, - &indices, + @ptrCast([*]*llvm.Value, &indices), @intCast(c_uint, indices.len), ) orelse return error.OutOfMemory; }, @@ -423,7 +423,7 @@ pub const Value = struct { }; /// Takes ownership of buffer - pub async fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array { + pub fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array { const u8_type = Type.Int.get_u8(comp); defer u8_type.base.base.deref(comp); diff --git a/src/all_types.hpp b/src/all_types.hpp index a5fa7241e3..4dac878315 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1565,7 +1565,7 @@ struct ZigFn { // in the case of async functions this is the implicit return type according to the // zig source code, not according to zig ir ZigType *src_implicit_return_type; - IrExecutable ir_executable; + IrExecutable *ir_executable; IrExecutable analyzed_executable; size_t prealloc_bbc; size_t prealloc_backward_branch_quota; @@ -2204,6 +2204,8 @@ struct ZigVar { bool src_is_const; bool gen_is_const; bool is_thread_local; + bool is_comptime_memoized; + bool is_comptime_memoized_value; }; struct ErrorTableEntry { diff --git a/src/analyze.cpp b/src/analyze.cpp index 894b2416f1..0f2df5835c 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -3275,14 +3275,15 @@ static void get_fully_qualified_decl_name(CodeGen *g, Buf *buf, Tld *tld, bool i } ZigFn *create_fn_raw(CodeGen *g, FnInline inline_value) { - ZigFn *fn_entry = allocate(1); + ZigFn *fn_entry = allocate(1, "ZigFn"); + fn_entry->ir_executable = allocate(1, "IrExecutablePass1"); fn_entry->prealloc_backward_branch_quota = default_backward_branch_quota; fn_entry->analyzed_executable.backward_branch_count = &fn_entry->prealloc_bbc; fn_entry->analyzed_executable.backward_branch_quota = &fn_entry->prealloc_backward_branch_quota; fn_entry->analyzed_executable.fn_entry = fn_entry; - fn_entry->ir_executable.fn_entry = fn_entry; + fn_entry->ir_executable->fn_entry = fn_entry; fn_entry->fn_inline = inline_value; return fn_entry; @@ -3792,6 +3793,16 @@ ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf return variable_entry; } +static void validate_export_var_type(CodeGen *g, ZigType* type, AstNode *source_node) { + switch (type->id) { + case ZigTypeIdMetaType: + add_node_error(g, source_node, buf_sprintf("cannot export variable of type 'type'")); + break; + default: + break; + } +} + static void resolve_decl_var(CodeGen *g, TldVar *tld_var, bool allow_lazy) { AstNode *source_node = tld_var->base.source_node; AstNodeVariableDeclaration *var_decl = &source_node->data.variable_declaration; @@ -3881,6 +3892,7 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var, bool allow_lazy) { } if (is_export) { + validate_export_var_type(g, type, source_node); add_var_export(g, tld_var->var, tld_var->var->name, GlobalLinkageIdStrong); } @@ -4599,7 +4611,7 @@ static void analyze_fn_ir(CodeGen *g, ZigFn *fn, AstNode *return_type_node) { assert(!fn_type->data.fn.is_generic); FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; - ZigType *block_return_type = ir_analyze(g, &fn->ir_executable, + ZigType *block_return_type = ir_analyze(g, fn->ir_executable, &fn->analyzed_executable, fn_type_id->return_type, return_type_node); fn->src_implicit_return_type = block_return_type; @@ -4695,7 +4707,7 @@ static void analyze_fn_body(CodeGen *g, ZigFn *fn_table_entry) { assert(!fn_type->data.fn.is_generic); ir_gen_fn(g, fn_table_entry); - if (fn_table_entry->ir_executable.first_err_trace_msg != nullptr) { + if (fn_table_entry->ir_executable->first_err_trace_msg != nullptr) { fn_table_entry->anal_state = FnAnalStateInvalid; return; } @@ -4703,7 +4715,7 @@ static void analyze_fn_body(CodeGen *g, ZigFn *fn_table_entry) { fprintf(stderr, "\n"); ast_render(stderr, fn_table_entry->body_node, 4); fprintf(stderr, "\nfn %s() { // (IR)\n", buf_ptr(&fn_table_entry->symbol_name)); - ir_print(g, stderr, &fn_table_entry->ir_executable, 4, IrPassSrc); + ir_print(g, stderr, fn_table_entry->ir_executable, 4, IrPassSrc); fprintf(stderr, "}\n"); } @@ -6442,20 +6454,31 @@ Error type_resolve(CodeGen *g, ZigType *ty, ResolveStatus status) { } bool ir_get_var_is_comptime(ZigVar *var) { + if (var->is_comptime_memoized) + return var->is_comptime_memoized_value; + + var->is_comptime_memoized = true; + // The is_comptime field can be left null, which means not comptime. - if (var->is_comptime == nullptr) - return false; + if (var->is_comptime == nullptr) { + var->is_comptime_memoized_value = false; + return var->is_comptime_memoized_value; + } // When the is_comptime field references an instruction that has to get analyzed, this // is the value. if (var->is_comptime->child != nullptr) { assert(var->is_comptime->child->value->type->id == ZigTypeIdBool); - return var->is_comptime->child->value->data.x_bool; + var->is_comptime_memoized_value = var->is_comptime->child->value->data.x_bool; + var->is_comptime = nullptr; + return var->is_comptime_memoized_value; } // As an optimization, is_comptime values which are constant are allowed // to be omitted from analysis. In this case, there is no child instruction // and we simply look at the unanalyzed const parent instruction. assert(var->is_comptime->value->type->id == ZigTypeIdBool); - return var->is_comptime->value->data.x_bool; + var->is_comptime_memoized_value = var->is_comptime->value->data.x_bool; + var->is_comptime = nullptr; + return var->is_comptime_memoized_value; } bool const_values_equal_ptr(ZigValue *a, ZigValue *b) { diff --git a/src/codegen.cpp b/src/codegen.cpp index 327ab76425..32dd6091f3 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -9563,7 +9563,11 @@ static void prepend_c_type_to_decl_list(CodeGen *g, GenH *gen_h, ZigType *type_e case ZigTypeIdVoid: case ZigTypeIdUnreachable: case ZigTypeIdBool: + g->c_want_stdbool = true; + return; case ZigTypeIdInt: + g->c_want_stdint = true; + return; case ZigTypeIdFloat: return; case ZigTypeIdOpaque: @@ -9644,7 +9648,6 @@ static void get_c_type(CodeGen *g, GenH *gen_h, ZigType *type_entry, Buf *out_bu break; case ZigTypeIdBool: buf_init_from_str(out_buf, "bool"); - g->c_want_stdbool = true; break; case ZigTypeIdUnreachable: buf_init_from_str(out_buf, "__attribute__((__noreturn__)) void"); @@ -9668,7 +9671,6 @@ static void get_c_type(CodeGen *g, GenH *gen_h, ZigType *type_entry, Buf *out_bu } break; case ZigTypeIdInt: - g->c_want_stdint = true; buf_resize(out_buf, 0); buf_appendf(out_buf, "%sint%" PRIu32 "_t", type_entry->data.integral.is_signed ? "" : "u", @@ -9780,113 +9782,7 @@ static Buf *preprocessor_mangle(Buf *src) { return result; } -static void gen_h_file(CodeGen *g) { - GenH gen_h_data = {0}; - GenH *gen_h = &gen_h_data; - - assert(!g->is_test_build); - assert(!g->disable_gen_h); - - Buf *out_h_path = buf_sprintf("%s" OS_SEP "%s.h", buf_ptr(g->output_dir), buf_ptr(g->root_out_name)); - - FILE *out_h = fopen(buf_ptr(out_h_path), "wb"); - if (!out_h) - zig_panic("unable to open %s: %s\n", buf_ptr(out_h_path), strerror(errno)); - - Buf *export_macro = nullptr; - if (g->is_dynamic) { - export_macro = preprocessor_mangle(buf_sprintf("%s_EXPORT", buf_ptr(g->root_out_name))); - buf_upcase(export_macro); - } - - Buf *extern_c_macro = preprocessor_mangle(buf_sprintf("%s_EXTERN_C", buf_ptr(g->root_out_name))); - buf_upcase(extern_c_macro); - - Buf h_buf = BUF_INIT; - buf_resize(&h_buf, 0); - for (size_t fn_def_i = 0; fn_def_i < g->fn_defs.length; fn_def_i += 1) { - ZigFn *fn_table_entry = g->fn_defs.at(fn_def_i); - - if (fn_table_entry->export_list.length == 0) - continue; - - FnTypeId *fn_type_id = &fn_table_entry->type_entry->data.fn.fn_type_id; - - Buf return_type_c = BUF_INIT; - get_c_type(g, gen_h, fn_type_id->return_type, &return_type_c); - - Buf *symbol_name; - if (fn_table_entry->export_list.length == 0) { - symbol_name = &fn_table_entry->symbol_name; - } else { - GlobalExport *fn_export = &fn_table_entry->export_list.items[0]; - symbol_name = &fn_export->name; - } - - buf_appendf(&h_buf, "%s %s %s(", - buf_ptr(g->is_dynamic ? export_macro : extern_c_macro), - buf_ptr(&return_type_c), - buf_ptr(symbol_name)); - - Buf param_type_c = BUF_INIT; - if (fn_type_id->param_count > 0) { - for (size_t param_i = 0; param_i < fn_type_id->param_count; param_i += 1) { - FnTypeParamInfo *param_info = &fn_type_id->param_info[param_i]; - AstNode *param_decl_node = get_param_decl_node(fn_table_entry, param_i); - Buf *param_name = param_decl_node->data.param_decl.name; - - const char *comma_str = (param_i == 0) ? "" : ", "; - const char *restrict_str = param_info->is_noalias ? "restrict" : ""; - get_c_type(g, gen_h, param_info->type, ¶m_type_c); - - if (param_info->type->id == ZigTypeIdArray) { - // Arrays decay to pointers - buf_appendf(&h_buf, "%s%s%s %s[]", comma_str, buf_ptr(¶m_type_c), - restrict_str, buf_ptr(param_name)); - } else { - buf_appendf(&h_buf, "%s%s%s %s", comma_str, buf_ptr(¶m_type_c), - restrict_str, buf_ptr(param_name)); - } - } - buf_appendf(&h_buf, ")"); - } else { - buf_appendf(&h_buf, "void)"); - } - - buf_appendf(&h_buf, ";\n"); - - } - - Buf *ifdef_dance_name = preprocessor_mangle(buf_sprintf("%s_H", buf_ptr(g->root_out_name))); - buf_upcase(ifdef_dance_name); - - fprintf(out_h, "#ifndef %s\n", buf_ptr(ifdef_dance_name)); - fprintf(out_h, "#define %s\n\n", buf_ptr(ifdef_dance_name)); - - if (g->c_want_stdbool) - fprintf(out_h, "#include \n"); - if (g->c_want_stdint) - fprintf(out_h, "#include \n"); - - fprintf(out_h, "\n"); - - fprintf(out_h, "#ifdef __cplusplus\n"); - fprintf(out_h, "#define %s extern \"C\"\n", buf_ptr(extern_c_macro)); - fprintf(out_h, "#else\n"); - fprintf(out_h, "#define %s\n", buf_ptr(extern_c_macro)); - fprintf(out_h, "#endif\n"); - fprintf(out_h, "\n"); - - if (g->is_dynamic) { - fprintf(out_h, "#if defined(_WIN32)\n"); - fprintf(out_h, "#define %s %s __declspec(dllimport)\n", buf_ptr(export_macro), buf_ptr(extern_c_macro)); - fprintf(out_h, "#else\n"); - fprintf(out_h, "#define %s %s __attribute__((visibility (\"default\")))\n", - buf_ptr(export_macro), buf_ptr(extern_c_macro)); - fprintf(out_h, "#endif\n"); - fprintf(out_h, "\n"); - } - +static void gen_h_file_types(CodeGen* g, GenH* gen_h, Buf* out_buf) { for (size_t type_i = 0; type_i < gen_h->types_to_declare.length; type_i += 1) { ZigType *type_entry = gen_h->types_to_declare.at(type_i); switch (type_entry->id) { @@ -9917,25 +9813,25 @@ static void gen_h_file(CodeGen *g) { case ZigTypeIdEnum: if (type_entry->data.enumeration.layout == ContainerLayoutExtern) { - fprintf(out_h, "enum %s {\n", buf_ptr(type_h_name(type_entry))); + buf_appendf(out_buf, "enum %s {\n", buf_ptr(type_h_name(type_entry))); for (uint32_t field_i = 0; field_i < type_entry->data.enumeration.src_field_count; field_i += 1) { TypeEnumField *enum_field = &type_entry->data.enumeration.fields[field_i]; Buf *value_buf = buf_alloc(); bigint_append_buf(value_buf, &enum_field->value, 10); - fprintf(out_h, " %s = %s", buf_ptr(enum_field->name), buf_ptr(value_buf)); + buf_appendf(out_buf, " %s = %s", buf_ptr(enum_field->name), buf_ptr(value_buf)); if (field_i != type_entry->data.enumeration.src_field_count - 1) { - fprintf(out_h, ","); + buf_appendf(out_buf, ","); } - fprintf(out_h, "\n"); + buf_appendf(out_buf, "\n"); } - fprintf(out_h, "};\n\n"); + buf_appendf(out_buf, "};\n\n"); } else { - fprintf(out_h, "enum %s;\n", buf_ptr(type_h_name(type_entry))); + buf_appendf(out_buf, "enum %s;\n\n", buf_ptr(type_h_name(type_entry))); } break; case ZigTypeIdStruct: if (type_entry->data.structure.layout == ContainerLayoutExtern) { - fprintf(out_h, "struct %s {\n", buf_ptr(type_h_name(type_entry))); + buf_appendf(out_buf, "struct %s {\n", buf_ptr(type_h_name(type_entry))); for (uint32_t field_i = 0; field_i < type_entry->data.structure.src_field_count; field_i += 1) { TypeStructField *struct_field = type_entry->data.structure.fields[field_i]; @@ -9943,43 +9839,194 @@ static void gen_h_file(CodeGen *g) { get_c_type(g, gen_h, struct_field->type_entry, type_name_buf); if (struct_field->type_entry->id == ZigTypeIdArray) { - fprintf(out_h, " %s %s[%" ZIG_PRI_u64 "];\n", buf_ptr(type_name_buf), + buf_appendf(out_buf, " %s %s[%" ZIG_PRI_u64 "];\n", buf_ptr(type_name_buf), buf_ptr(struct_field->name), struct_field->type_entry->data.array.len); } else { - fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(struct_field->name)); + buf_appendf(out_buf, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(struct_field->name)); } } - fprintf(out_h, "};\n\n"); + buf_appendf(out_buf, "};\n\n"); } else { - fprintf(out_h, "struct %s;\n", buf_ptr(type_h_name(type_entry))); + buf_appendf(out_buf, "struct %s;\n\n", buf_ptr(type_h_name(type_entry))); } break; case ZigTypeIdUnion: if (type_entry->data.unionation.layout == ContainerLayoutExtern) { - fprintf(out_h, "union %s {\n", buf_ptr(type_h_name(type_entry))); + buf_appendf(out_buf, "union %s {\n", buf_ptr(type_h_name(type_entry))); for (uint32_t field_i = 0; field_i < type_entry->data.unionation.src_field_count; field_i += 1) { TypeUnionField *union_field = &type_entry->data.unionation.fields[field_i]; Buf *type_name_buf = buf_alloc(); get_c_type(g, gen_h, union_field->type_entry, type_name_buf); - fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(union_field->name)); + buf_appendf(out_buf, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(union_field->name)); } - fprintf(out_h, "};\n\n"); + buf_appendf(out_buf, "};\n\n"); } else { - fprintf(out_h, "union %s;\n", buf_ptr(type_h_name(type_entry))); + buf_appendf(out_buf, "union %s;\n\n", buf_ptr(type_h_name(type_entry))); } break; case ZigTypeIdOpaque: - fprintf(out_h, "struct %s;\n\n", buf_ptr(type_h_name(type_entry))); + buf_appendf(out_buf, "struct %s;\n\n", buf_ptr(type_h_name(type_entry))); break; } } +} - fprintf(out_h, "%s", buf_ptr(&h_buf)); +static void gen_h_file_functions(CodeGen* g, GenH* gen_h, Buf* out_buf, Buf* export_macro) { + for (size_t fn_def_i = 0; fn_def_i < g->fn_defs.length; fn_def_i += 1) { + ZigFn *fn_table_entry = g->fn_defs.at(fn_def_i); - fprintf(out_h, "\n#endif\n"); + if (fn_table_entry->export_list.length == 0) + continue; + + FnTypeId *fn_type_id = &fn_table_entry->type_entry->data.fn.fn_type_id; + + Buf return_type_c = BUF_INIT; + get_c_type(g, gen_h, fn_type_id->return_type, &return_type_c); + + Buf *symbol_name; + if (fn_table_entry->export_list.length == 0) { + symbol_name = &fn_table_entry->symbol_name; + } else { + GlobalExport *fn_export = &fn_table_entry->export_list.items[0]; + symbol_name = &fn_export->name; + } + + if (export_macro != nullptr) { + buf_appendf(out_buf, "%s %s %s(", + buf_ptr(export_macro), + buf_ptr(&return_type_c), + buf_ptr(symbol_name)); + } else { + buf_appendf(out_buf, "%s %s(", + buf_ptr(&return_type_c), + buf_ptr(symbol_name)); + } + + Buf param_type_c = BUF_INIT; + if (fn_type_id->param_count > 0) { + for (size_t param_i = 0; param_i < fn_type_id->param_count; param_i += 1) { + FnTypeParamInfo *param_info = &fn_type_id->param_info[param_i]; + AstNode *param_decl_node = get_param_decl_node(fn_table_entry, param_i); + Buf *param_name = param_decl_node->data.param_decl.name; + + const char *comma_str = (param_i == 0) ? "" : ", "; + const char *restrict_str = param_info->is_noalias ? "restrict" : ""; + get_c_type(g, gen_h, param_info->type, ¶m_type_c); + + if (param_info->type->id == ZigTypeIdArray) { + // Arrays decay to pointers + buf_appendf(out_buf, "%s%s%s %s[]", comma_str, buf_ptr(¶m_type_c), + restrict_str, buf_ptr(param_name)); + } else { + buf_appendf(out_buf, "%s%s%s %s", comma_str, buf_ptr(¶m_type_c), + restrict_str, buf_ptr(param_name)); + } + } + buf_appendf(out_buf, ")"); + } else { + buf_appendf(out_buf, "void)"); + } + + buf_appendf(out_buf, ";\n"); + } +} + +static void gen_h_file_variables(CodeGen* g, GenH* gen_h, Buf* h_buf, Buf* export_macro) { + for (size_t exp_var_i = 0; exp_var_i < g->global_vars.length; exp_var_i += 1) { + ZigVar* var = g->global_vars.at(exp_var_i)->var; + if (var->export_list.length == 0) + continue; + + Buf var_type_c = BUF_INIT; + get_c_type(g, gen_h, var->var_type, &var_type_c); + + if (export_macro != nullptr) { + buf_appendf(h_buf, "extern %s %s %s;\n", + buf_ptr(export_macro), + buf_ptr(&var_type_c), + var->name); + } else { + buf_appendf(h_buf, "extern %s %s;\n", + buf_ptr(&var_type_c), + var->name); + } + } +} + +static void gen_h_file(CodeGen *g) { + GenH gen_h_data = {0}; + GenH *gen_h = &gen_h_data; + + assert(!g->is_test_build); + assert(!g->disable_gen_h); + + Buf *out_h_path = buf_sprintf("%s" OS_SEP "%s.h", buf_ptr(g->output_dir), buf_ptr(g->root_out_name)); + + FILE *out_h = fopen(buf_ptr(out_h_path), "wb"); + if (!out_h) + zig_panic("unable to open %s: %s\n", buf_ptr(out_h_path), strerror(errno)); + + Buf *export_macro = nullptr; + if (g->is_dynamic) { + export_macro = preprocessor_mangle(buf_sprintf("%s_EXPORT", buf_ptr(g->root_out_name))); + buf_upcase(export_macro); + } + + Buf fns_buf = BUF_INIT; + buf_resize(&fns_buf, 0); + gen_h_file_functions(g, gen_h, &fns_buf, export_macro); + + Buf vars_buf = BUF_INIT; + buf_resize(&vars_buf, 0); + gen_h_file_variables(g, gen_h, &vars_buf, export_macro); + + // Types will be populated by exported functions and variables so it has to run last. + Buf types_buf = BUF_INIT; + buf_resize(&types_buf, 0); + gen_h_file_types(g, gen_h, &types_buf); + + Buf *ifdef_dance_name = preprocessor_mangle(buf_sprintf("%s_H", buf_ptr(g->root_out_name))); + buf_upcase(ifdef_dance_name); + + fprintf(out_h, "#ifndef %s\n", buf_ptr(ifdef_dance_name)); + fprintf(out_h, "#define %s\n\n", buf_ptr(ifdef_dance_name)); + + if (g->c_want_stdbool) + fprintf(out_h, "#include \n"); + if (g->c_want_stdint) + fprintf(out_h, "#include \n"); + + fprintf(out_h, "\n"); + + if (g->is_dynamic) { + fprintf(out_h, "#if defined(_WIN32)\n"); + fprintf(out_h, "#define %s __declspec(dllimport)\n", buf_ptr(export_macro)); + fprintf(out_h, "#else\n"); + fprintf(out_h, "#define %s __attribute__((visibility (\"default\")))\n", + buf_ptr(export_macro)); + fprintf(out_h, "#endif\n"); + fprintf(out_h, "\n"); + } + + fprintf(out_h, "%s", buf_ptr(&types_buf)); + + fprintf(out_h, "#ifdef __cplusplus\n"); + fprintf(out_h, "extern \"C\" {\n"); + fprintf(out_h, "#endif\n"); + fprintf(out_h, "\n"); + + fprintf(out_h, "%s\n", buf_ptr(&fns_buf)); + + fprintf(out_h, "#ifdef __cplusplus\n"); + fprintf(out_h, "} // extern \"C\"\n"); + fprintf(out_h, "#endif\n\n"); + + fprintf(out_h, "%s\n", buf_ptr(&vars_buf)); + + fprintf(out_h, "#endif // %s\n", buf_ptr(ifdef_dance_name)); if (fclose(out_h)) zig_panic("unable to close h file: %s", strerror(errno)); diff --git a/src/ir.cpp b/src/ir.cpp index 8f79841df3..a2c5a4318a 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -41,6 +41,7 @@ struct IrAnalyze { ZigList src_implicit_return_type_list; ZigList resume_stack; IrBasicBlock *const_predecessor_bb; + size_t ref_count; // For the purpose of using in a debugger void dump(); @@ -74,6 +75,7 @@ enum ConstCastResultId { ConstCastResultIdPtrLens, ConstCastResultIdCV, ConstCastResultIdPtrSentinel, + ConstCastResultIdIntShorten, }; struct ConstCastOnly; @@ -100,6 +102,7 @@ struct ConstCastBadAllowsZero; struct ConstCastBadNullTermArrays; struct ConstCastBadCV; struct ConstCastPtrSentinel; +struct ConstCastIntShorten; struct ConstCastOnly { ConstCastResultId id; @@ -120,6 +123,7 @@ struct ConstCastOnly { ConstCastBadNullTermArrays *sentinel_arrays; ConstCastBadCV *bad_cv; ConstCastPtrSentinel *bad_ptr_sentinel; + ConstCastIntShorten *int_shorten; } data; }; @@ -189,6 +193,11 @@ struct ConstCastPtrSentinel { ZigType *actual_type; }; +struct ConstCastIntShorten { + ZigType *wanted_type; + ZigType *actual_type; +}; + static IrInstruction *ir_gen_node(IrBuilder *irb, AstNode *node, Scope *scope); static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *scope, LVal lval, ResultLoc *result_loc); @@ -248,6 +257,381 @@ static IrInstruction *ir_analyze_inferred_field_ptr(IrAnalyze *ira, Buf *field_n IrInstruction *source_instr, IrInstruction *container_ptr, ZigType *container_type); static ResultLoc *no_result_loc(void); +static void destroy_instruction(IrInstruction *inst) { +#ifdef ZIG_ENABLE_MEM_PROFILE + const char *name = ir_instruction_type_str(inst->id); +#else + const char *name = nullptr; +#endif + switch (inst->id) { + case IrInstructionIdInvalid: + zig_unreachable(); + case IrInstructionIdReturn: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdConst: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBinOp: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdMergeErrSets: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdDeclVarSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCast: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCallSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCallGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdUnOp: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCondBr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdPhi: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdContainerInitList: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdContainerInitFields: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdUnreachable: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdElemPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdVarPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdReturnPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdLoadPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdLoadPtrGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdStorePtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdVectorStoreElem: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTypeOf: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFieldPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdStructFieldPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdUnionFieldPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSetCold: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSetRuntimeSafety: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSetFloatMode: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdArrayType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSliceType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAnyFrameType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdGlobalAsm: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAsm: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSizeOf: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTestNonNull: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdOptionalUnwrapPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdPopCount: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdClz: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCtz: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBswap: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBitReverse: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSwitchBr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSwitchVar: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSwitchElseVar: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSwitchTarget: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdUnionTag: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdImport: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdRef: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdRefGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCompileErr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCompileLog: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdErrName: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCImport: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCInclude: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCDefine: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCUndef: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdEmbedFile: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCmpxchgSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCmpxchgGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFence: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTruncate: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdIntCast: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFloatCast: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdErrSetCast: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFromBytes: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdToBytes: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdIntToFloat: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFloatToInt: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBoolToInt: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdIntType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdVectorType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdShuffleVector: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSplatSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSplatGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBoolNot: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdMemset: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdMemcpy: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSliceSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSliceGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdMemberCount: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdMemberType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdMemberName: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBreakpoint: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdReturnAddress: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFrameAddress: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFrameHandle: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFrameType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFrameSizeSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFrameSizeGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAlignOf: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdOverflowOp: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTestErrSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTestErrGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdUnwrapErrCode: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdUnwrapErrPayload: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdOptionalWrap: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdErrWrapCode: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdErrWrapPayload: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFnProto: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTestComptime: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdPtrCastSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdPtrCastGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBitCastSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBitCastGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdWidenOrShorten: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdPtrToInt: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdIntToPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdIntToEnum: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdIntToErr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdErrToInt: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCheckSwitchProngs: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCheckStatementIsVoid: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTypeName: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTagName: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdPtrType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdDeclRef: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdPanic: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFieldParentPtr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdByteOffsetOf: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdBitOffsetOf: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTypeInfo: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdHasField: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTypeId: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSetEvalBranchQuota: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAlignCast: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdImplicitCast: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdResolveResult: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdResetResult: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdOpaqueType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSetAlignStack: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdArgType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdTagType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdExport: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdErrorReturnTrace: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdErrorUnion: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAtomicRmw: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSaveErrRetAddr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAddImplicitReturnType: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdFloatOp: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdMulAdd: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAtomicLoad: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAtomicStore: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdEnumToInt: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdCheckRuntimeScope: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdDeclVarGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdArrayToVector: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdVectorToArray: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdPtrOfArrayToSlice: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAssertZero: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAssertNonNull: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdResizeSlice: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdHasDecl: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdUndeclaredIdent: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAllocaSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAllocaGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdEndExpr: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdUnionInitNamedField: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSuspendBegin: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSuspendFinish: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdResume: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAwaitSrc: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdAwaitGen: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSpillBegin: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdSpillEnd: + return destroy(reinterpret_cast(inst), name); + case IrInstructionIdVectorExtractElem: + return destroy(reinterpret_cast(inst), name); + } + zig_unreachable(); +} + +static void ira_ref(IrAnalyze *ira) { + ira->ref_count += 1; +} +static void ira_deref(IrAnalyze *ira) { + if (ira->ref_count > 1) { + ira->ref_count -= 1; + return; + } + assert(ira->ref_count != 0); + + for (size_t bb_i = 0; bb_i < ira->old_irb.exec->basic_block_list.length; bb_i += 1) { + IrBasicBlock *pass1_bb = ira->old_irb.exec->basic_block_list.items[bb_i]; + for (size_t inst_i = 0; inst_i < pass1_bb->instruction_list.length; inst_i += 1) { + IrInstruction *pass1_inst = pass1_bb->instruction_list.items[inst_i]; + destroy_instruction(pass1_inst); + } + destroy(pass1_bb, "IrBasicBlock"); + } + ira->old_irb.exec->basic_block_list.deinit(); + ira->old_irb.exec->tld_list.deinit(); + // cannot destroy here because of var->owner_exec + //destroy(ira->old_irb.exec, "IrExecutablePass1"); + ira->src_implicit_return_type_list.deinit(); + ira->resume_stack.deinit(); + ira->exec_context.mem_slot_list.deinit(); + destroy(ira, "IrAnalyze"); +} + static ZigValue *const_ptr_pointee_unchecked(CodeGen *g, ZigValue *const_val) { assert(get_src_ptr_type(const_val->type) != nullptr); assert(const_val->special == ConstValSpecialStatic); @@ -4186,7 +4570,7 @@ static IrInstruction *ir_gen_bool_and(IrBuilder *irb, Scope *scope, AstNode *nod IrInstruction **incoming_values = allocate(2); incoming_values[0] = val1; incoming_values[1] = val2; - IrBasicBlock **incoming_blocks = allocate(2); + IrBasicBlock **incoming_blocks = allocate(2, "IrBasicBlock *"); incoming_blocks[0] = post_val1_block; incoming_blocks[1] = post_val2_block; @@ -4277,7 +4661,7 @@ static IrInstruction *ir_gen_orelse(IrBuilder *irb, Scope *parent_scope, AstNode IrInstruction **incoming_values = allocate(2); incoming_values[0] = null_result; incoming_values[1] = unwrapped_payload; - IrBasicBlock **incoming_blocks = allocate(2); + IrBasicBlock **incoming_blocks = allocate(2, "IrBasicBlock *"); incoming_blocks[0] = after_null_block; incoming_blocks[1] = after_ok_block; IrInstruction *phi = ir_build_phi(irb, parent_scope, node, 2, incoming_blocks, incoming_values, peer_parent); @@ -6044,7 +6428,7 @@ static IrInstruction *ir_gen_if_bool_expr(IrBuilder *irb, Scope *scope, AstNode IrInstruction **incoming_values = allocate(2); incoming_values[0] = then_expr_result; incoming_values[1] = else_expr_result; - IrBasicBlock **incoming_blocks = allocate(2); + IrBasicBlock **incoming_blocks = allocate(2, "IrBasicBlock *"); incoming_blocks[0] = after_then_block; incoming_blocks[1] = after_else_block; @@ -7398,7 +7782,7 @@ static IrInstruction *ir_gen_if_optional_expr(IrBuilder *irb, Scope *scope, AstN IrInstruction **incoming_values = allocate(2); incoming_values[0] = then_expr_result; incoming_values[1] = else_expr_result; - IrBasicBlock **incoming_blocks = allocate(2); + IrBasicBlock **incoming_blocks = allocate(2, "IrBasicBlock *"); incoming_blocks[0] = after_then_block; incoming_blocks[1] = after_else_block; @@ -7495,7 +7879,7 @@ static IrInstruction *ir_gen_if_err_expr(IrBuilder *irb, Scope *scope, AstNode * IrInstruction **incoming_values = allocate(2); incoming_values[0] = then_expr_result; incoming_values[1] = else_expr_result; - IrBasicBlock **incoming_blocks = allocate(2); + IrBasicBlock **incoming_blocks = allocate(2, "IrBasicBlock *"); incoming_blocks[0] = after_then_block; incoming_blocks[1] = after_else_block; @@ -8092,7 +8476,7 @@ static IrInstruction *ir_gen_catch(IrBuilder *irb, Scope *parent_scope, AstNode IrInstruction **incoming_values = allocate(2); incoming_values[0] = err_result; incoming_values[1] = unwrapped_payload; - IrBasicBlock **incoming_blocks = allocate(2); + IrBasicBlock **incoming_blocks = allocate(2, "IrBasicBlock *"); incoming_blocks[0] = after_err_block; incoming_blocks[1] = after_ok_block; IrInstruction *phi = ir_build_phi(irb, parent_scope, node, 2, incoming_blocks, incoming_values, peer_parent); @@ -8680,7 +9064,7 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec bool ir_gen_fn(CodeGen *codegen, ZigFn *fn_entry) { assert(fn_entry); - IrExecutable *ir_executable = &fn_entry->ir_executable; + IrExecutable *ir_executable = fn_entry->ir_executable; AstNode *body_node = fn_entry->body_node; assert(fn_entry->child_scope); @@ -10224,6 +10608,14 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted return result; } + if (wanted_type->id == ZigTypeIdInt && actual_type->id == ZigTypeIdInt) { + result.id = ConstCastResultIdIntShorten; + result.data.int_shorten = allocate_nonzero(1); + result.data.int_shorten->wanted_type = wanted_type; + result.data.int_shorten->actual_type = actual_type; + return result; + } + result.id = ConstCastResultIdType; result.data.type_mismatch = allocate_nonzero(1); result.data.type_mismatch->wanted_type = wanted_type; @@ -11490,7 +11882,7 @@ ZigValue *ir_eval_const_value(CodeGen *codegen, Scope *scope, AstNode *node, if (expected_type != nullptr && type_is_invalid(expected_type)) return codegen->invalid_instruction->value; - IrExecutable *ir_executable = allocate(1); + IrExecutable *ir_executable = allocate(1, "IrExecutablePass1"); ir_executable->source_node = source_node; ir_executable->parent_exec = parent_exec; ir_executable->name = exec_name; @@ -11512,7 +11904,7 @@ ZigValue *ir_eval_const_value(CodeGen *codegen, Scope *scope, AstNode *node, ir_print(codegen, stderr, ir_executable, 2, IrPassSrc); fprintf(stderr, "}\n"); } - IrExecutable *analyzed_executable = allocate(1); + IrExecutable *analyzed_executable = allocate(1, "IrExecutablePass2"); analyzed_executable->source_node = source_node; analyzed_executable->parent_exec = parent_exec; analyzed_executable->source_exec = ir_executable; @@ -12641,6 +13033,17 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa add_error_note(ira->codegen, parent_msg, source_node, buf_sprintf("calling convention mismatch")); break; + case ConstCastResultIdIntShorten: { + ZigType *wanted_type = cast_result->data.int_shorten->wanted_type; + ZigType *actual_type = cast_result->data.int_shorten->actual_type; + const char *wanted_signed = wanted_type->data.integral.is_signed ? "signed" : "unsigned"; + const char *actual_signed = actual_type->data.integral.is_signed ? "signed" : "unsigned"; + add_error_note(ira->codegen, parent_msg, source_node, + buf_sprintf("%s %" PRIu32 "-bit int cannot represent all possible %s %" PRIu32 "-bit values", + wanted_signed, wanted_type->data.integral.bit_count, + actual_signed, actual_type->data.integral.bit_count)); + break; + } case ConstCastResultIdFnAlign: // TODO case ConstCastResultIdFnVarArgs: // TODO case ConstCastResultIdFnReturnType: // TODO @@ -15597,6 +16000,7 @@ static IrInstruction *ir_analyze_instruction_decl_var(IrAnalyze *ira, assert(var->mem_slot_index < ira->exec_context.mem_slot_list.length); ZigValue *mem_slot = ira->exec_context.mem_slot_list.at(var->mem_slot_index); copy_const_val(mem_slot, init_val, !is_comptime_var || var->gen_is_const); + ira_ref(var->owner_exec->analysis); if (is_comptime_var || (var_class_requires_const && var->gen_is_const)) { return ir_const_void(ira, &decl_var_instruction->base); @@ -15869,8 +16273,8 @@ static IrInstruction *ir_analyze_instruction_error_union(IrAnalyze *ira, IrInstruction *result = ir_const(ira, &instruction->base, ira->codegen->builtin_types.entry_type); result->value->special = ConstValSpecialLazy; - LazyValueErrUnionType *lazy_err_union_type = allocate(1); - lazy_err_union_type->ira = ira; + LazyValueErrUnionType *lazy_err_union_type = allocate(1, "LazyValueErrUnionType"); + lazy_err_union_type->ira = ira; ira_ref(ira); result->value->data.x_lazy = &lazy_err_union_type->base; lazy_err_union_type->base.id = LazyValueIdErrUnionType; @@ -17368,8 +17772,8 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c if (type_is_invalid(impl_fn->type_entry)) return ira->codegen->invalid_instruction; - impl_fn->ir_executable.source_node = call_instruction->base.source_node; - impl_fn->ir_executable.parent_exec = ira->new_irb.exec; + impl_fn->ir_executable->source_node = call_instruction->base.source_node; + impl_fn->ir_executable->parent_exec = ira->new_irb.exec; impl_fn->analyzed_executable.source_node = call_instruction->base.source_node; impl_fn->analyzed_executable.parent_exec = ira->new_irb.exec; impl_fn->analyzed_executable.backward_branch_quota = ira->new_irb.exec->backward_branch_quota; @@ -17722,8 +18126,8 @@ static IrInstruction *ir_analyze_optional_type(IrAnalyze *ira, IrInstructionUnOp IrInstruction *result = ir_const(ira, &instruction->base, ira->codegen->builtin_types.entry_type); result->value->special = ConstValSpecialLazy; - LazyValueOptType *lazy_opt_type = allocate(1); - lazy_opt_type->ira = ira; + LazyValueOptType *lazy_opt_type = allocate(1, "LazyValueOptType"); + lazy_opt_type->ira = ira; ira_ref(ira); result->value->data.x_lazy = &lazy_opt_type->base; lazy_opt_type->base.id = LazyValueIdOptType; @@ -19668,8 +20072,8 @@ static IrInstruction *ir_analyze_instruction_slice_type(IrAnalyze *ira, IrInstruction *result = ir_const(ira, &slice_type_instruction->base, ira->codegen->builtin_types.entry_type); result->value->special = ConstValSpecialLazy; - LazyValueSliceType *lazy_slice_type = allocate(1); - lazy_slice_type->ira = ira; + LazyValueSliceType *lazy_slice_type = allocate(1, "LazyValueSliceType"); + lazy_slice_type->ira = ira; ira_ref(ira); result->value->data.x_lazy = &lazy_slice_type->base; lazy_slice_type->base.id = LazyValueIdSliceType; @@ -19828,8 +20232,8 @@ static IrInstruction *ir_analyze_instruction_size_of(IrAnalyze *ira, IrInstructi IrInstruction *result = ir_const(ira, &instruction->base, ira->codegen->builtin_types.entry_num_lit_int); result->value->special = ConstValSpecialLazy; - LazyValueSizeOf *lazy_size_of = allocate(1); - lazy_size_of->ira = ira; + LazyValueSizeOf *lazy_size_of = allocate(1, "LazyValueSizeOf"); + lazy_size_of->ira = ira; ira_ref(ira); result->value->data.x_lazy = &lazy_size_of->base; lazy_size_of->base.id = LazyValueIdSizeOf; @@ -24556,8 +24960,8 @@ static IrInstruction *ir_analyze_instruction_align_of(IrAnalyze *ira, IrInstruct IrInstruction *result = ir_const(ira, &instruction->base, ira->codegen->builtin_types.entry_num_lit_int); result->value->special = ConstValSpecialLazy; - LazyValueAlignOf *lazy_align_of = allocate(1); - lazy_align_of->ira = ira; + LazyValueAlignOf *lazy_align_of = allocate(1, "LazyValueAlignOf"); + lazy_align_of->ira = ira; ira_ref(ira); result->value->data.x_lazy = &lazy_align_of->base; lazy_align_of->base.id = LazyValueIdAlignOf; @@ -25040,8 +25444,8 @@ static IrInstruction *ir_analyze_instruction_fn_proto(IrAnalyze *ira, IrInstruct IrInstruction *result = ir_const(ira, &instruction->base, ira->codegen->builtin_types.entry_type); result->value->special = ConstValSpecialLazy; - LazyValueFnType *lazy_fn_type = allocate(1); - lazy_fn_type->ira = ira; + LazyValueFnType *lazy_fn_type = allocate(1, "LazyValueFnType"); + lazy_fn_type->ira = ira; ira_ref(ira); result->value->data.x_lazy = &lazy_fn_type->base; lazy_fn_type->base.id = LazyValueIdFnType; @@ -26081,8 +26485,8 @@ static IrInstruction *ir_analyze_instruction_ptr_type(IrAnalyze *ira, IrInstruct IrInstruction *result = ir_const(ira, &instruction->base, ira->codegen->builtin_types.entry_type); result->value->special = ConstValSpecialLazy; - LazyValuePtrType *lazy_ptr_type = allocate(1); - lazy_ptr_type->ira = ira; + LazyValuePtrType *lazy_ptr_type = allocate(1, "LazyValuePtrType"); + lazy_ptr_type->ira = ira; ira_ref(ira); result->value->data.x_lazy = &lazy_ptr_type->base; lazy_ptr_type->base.id = LazyValueIdPtrType; @@ -27551,7 +27955,8 @@ ZigType *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutable *new_ assert(old_exec->first_err_trace_msg == nullptr); assert(expected_type == nullptr || !type_is_invalid(expected_type)); - IrAnalyze *ira = allocate(1); + IrAnalyze *ira = allocate(1, "IrAnalyze"); + ira->ref_count = 1; old_exec->analysis = ira; ira->codegen = codegen; @@ -27618,6 +28023,7 @@ ZigType *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutable *new_ ira->instruction_index += 1; } + ZigType *res_type; if (new_exec->first_err_trace_msg != nullptr) { codegen->trace_err = new_exec->first_err_trace_msg; if (codegen->trace_err != nullptr && new_exec->source_node != nullptr && @@ -27627,13 +28033,18 @@ ZigType *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutable *new_ codegen->trace_err = add_error_note(codegen, codegen->trace_err, new_exec->source_node, buf_create_from_str("referenced here")); } - return ira->codegen->builtin_types.entry_invalid; + res_type = ira->codegen->builtin_types.entry_invalid; } else if (ira->src_implicit_return_type_list.length == 0) { - return codegen->builtin_types.entry_unreachable; + res_type = codegen->builtin_types.entry_unreachable; } else { - return ir_resolve_peer_types(ira, expected_type_source_node, expected_type, ira->src_implicit_return_type_list.items, + res_type = ir_resolve_peer_types(ira, expected_type_source_node, expected_type, ira->src_implicit_return_type_list.items, ira->src_implicit_return_type_list.length); } + + // It is now safe to free Pass 1 IR instructions. + ira_deref(ira); + + return res_type; } bool ir_has_side_effects(IrInstruction *instruction) { @@ -27969,6 +28380,8 @@ static Error ir_resolve_lazy_raw(AstNode *source_node, ZigValue *val) { val->special = ConstValSpecialStatic; assert(val->type->id == ZigTypeIdComptimeInt || val->type->id == ZigTypeIdInt); bigint_init_unsigned(&val->data.x_bigint, align_in_bytes); + + // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. return ErrorNone; } case LazyValueIdSizeOf: { @@ -28024,6 +28437,8 @@ static Error ir_resolve_lazy_raw(AstNode *source_node, ZigValue *val) { val->special = ConstValSpecialStatic; assert(val->type->id == ZigTypeIdComptimeInt || val->type->id == ZigTypeIdInt); bigint_init_unsigned(&val->data.x_bigint, abi_size); + + // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. return ErrorNone; } case LazyValueIdSliceType: { @@ -28102,6 +28517,8 @@ static Error ir_resolve_lazy_raw(AstNode *source_node, ZigValue *val) { val->special = ConstValSpecialStatic; assert(val->type->id == ZigTypeIdMetaType); val->data.x_type = get_slice_type(ira->codegen, slice_ptr_type); + + // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. return ErrorNone; } case LazyValueIdPtrType: { @@ -28173,6 +28590,8 @@ static Error ir_resolve_lazy_raw(AstNode *source_node, ZigValue *val) { lazy_ptr_type->bit_offset_in_host, lazy_ptr_type->host_int_bytes, allow_zero, VECTOR_INDEX_NONE, nullptr, sentinel_val); val->special = ConstValSpecialStatic; + + // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. return ErrorNone; } case LazyValueIdOptType: { @@ -28195,16 +28614,21 @@ static Error ir_resolve_lazy_raw(AstNode *source_node, ZigValue *val) { assert(val->type->id == ZigTypeIdMetaType); val->data.x_type = get_optional_type(ira->codegen, payload_type); val->special = ConstValSpecialStatic; + + // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. return ErrorNone; } case LazyValueIdFnType: { LazyValueFnType *lazy_fn_type = reinterpret_cast(val->data.x_lazy); - ZigType *fn_type = ir_resolve_lazy_fn_type(lazy_fn_type->ira, source_node, lazy_fn_type); + IrAnalyze *ira = lazy_fn_type->ira; + ZigType *fn_type = ir_resolve_lazy_fn_type(ira, source_node, lazy_fn_type); if (fn_type == nullptr) return ErrorSemanticAnalyzeFail; val->special = ConstValSpecialStatic; assert(val->type->id == ZigTypeIdMetaType); val->data.x_type = fn_type; + + // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. return ErrorNone; } case LazyValueIdErrUnionType: { @@ -28233,6 +28657,8 @@ static Error ir_resolve_lazy_raw(AstNode *source_node, ZigValue *val) { assert(val->type->id == ZigTypeIdMetaType); val->data.x_type = get_error_union_type(ira->codegen, err_set_type, payload_type); val->special = ConstValSpecialStatic; + + // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. return ErrorNone; } } diff --git a/src/libc_installation.cpp b/src/libc_installation.cpp index 6ae6bb9075..2adc1cb69d 100644 --- a/src/libc_installation.cpp +++ b/src/libc_installation.cpp @@ -389,7 +389,7 @@ static Error zig_libc_find_native_msvc_include_dir(ZigLibCInstallation *self, Zi } Buf search_path = BUF_INIT; buf_init_from_mem(&search_path, sdk->msvc_lib_dir_ptr, sdk->msvc_lib_dir_len); - buf_append_str(&search_path, "\\..\\..\\include"); + buf_append_str(&search_path, "..\\..\\include"); Buf *vcruntime_path = buf_sprintf("%s\\vcruntime.h", buf_ptr(&search_path)); bool exists; diff --git a/src/list.hpp b/src/list.hpp index 59782b46a8..4b2833843b 100644 --- a/src/list.hpp +++ b/src/list.hpp @@ -13,7 +13,7 @@ template struct ZigList { void deinit() { - free(items); + deallocate(items, capacity); } void append(const T& item) { ensure_capacity(length + 1); diff --git a/src/memory_profiling.cpp b/src/memory_profiling.cpp index a44dfc4450..4bd4cea7ba 100644 --- a/src/memory_profiling.cpp +++ b/src/memory_profiling.cpp @@ -35,7 +35,9 @@ static const char *get_default_name(const char *name_or_null, size_t type_size) if (name_or_null != nullptr) return name_or_null; if (type_size >= unknown_names.length) { table_active = false; - unknown_names.resize(type_size + 1); + while (type_size >= unknown_names.length) { + unknown_names.append(nullptr); + } table_active = true; } if (unknown_names.at(type_size) == nullptr) { @@ -66,7 +68,8 @@ void memprof_dealloc(const char *name, size_t count, size_t type_size) { name = get_default_name(name, type_size); auto existing_entry = usage_table.maybe_get(name); if (existing_entry == nullptr) { - zig_panic("deallocated more than allocated; compromised memory usage stats"); + zig_panic("deallocated name '%s' (size %zu) not found in allocated table; compromised memory usage stats", + name, type_size); } if (existing_entry->value.type_size != type_size) { zig_panic("deallocated name '%s' does not match expected type size %zu", name, type_size); diff --git a/src/os.cpp b/src/os.cpp index da1f4ecfb0..f6a0b4fbd8 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -1554,7 +1554,7 @@ void os_stderr_set_color(TermColor color) { Error os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchType platform_type) { #if defined(ZIG_OS_WINDOWS) buf_resize(output_buf, 0); - buf_appendf(output_buf, "%s\\Lib\\%s\\ucrt\\", sdk->path10_ptr, sdk->version10_ptr); + buf_appendf(output_buf, "%sLib\\%s\\ucrt\\", sdk->path10_ptr, sdk->version10_ptr); switch (platform_type) { case ZigLLVM_x86: buf_append_str(output_buf, "x86\\"); @@ -1586,7 +1586,7 @@ Error os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_Ar Error os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf* output_buf) { #if defined(ZIG_OS_WINDOWS) buf_resize(output_buf, 0); - buf_appendf(output_buf, "%s\\Include\\%s\\ucrt", sdk->path10_ptr, sdk->version10_ptr); + buf_appendf(output_buf, "%sInclude\\%s\\ucrt", sdk->path10_ptr, sdk->version10_ptr); if (GetFileAttributesA(buf_ptr(output_buf)) != INVALID_FILE_ATTRIBUTES) { return ErrorNone; } @@ -1603,7 +1603,7 @@ Error os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_Arch #if defined(ZIG_OS_WINDOWS) { buf_resize(output_buf, 0); - buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", sdk->path10_ptr, sdk->version10_ptr); + buf_appendf(output_buf, "%sLib\\%s\\um\\", sdk->path10_ptr, sdk->version10_ptr); switch (platform_type) { case ZigLLVM_x86: buf_append_str(output_buf, "x86\\"); @@ -1626,7 +1626,7 @@ Error os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_Arch } { buf_resize(output_buf, 0); - buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", sdk->path81_ptr, sdk->version81_ptr); + buf_appendf(output_buf, "%sLib\\%s\\um\\", sdk->path81_ptr, sdk->version81_ptr); switch (platform_type) { case ZigLLVM_x86: buf_append_str(output_buf, "x86\\"); diff --git a/src/util.hpp b/src/util.hpp index 79bebd3355..91535cce18 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -165,7 +165,7 @@ static inline void deallocate(T *old, size_t count, const char *name = nullptr) template static inline void destroy(T *old, const char *name = nullptr) { - return deallocate(old, 1); + return deallocate(old, 1, name); } template diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 4008ff19e1..9bd6d29c26 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1670,10 +1670,17 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ var spartan_count: u16 = 300; \\ var byte: u8 = spartan_count; \\} + \\export fn entry4() void { + \\ var signed: i8 = -1; + \\ var unsigned: u64 = signed; + \\} , "tmp.zig:3:31: error: integer value 300 cannot be coerced to type 'u8'", "tmp.zig:7:22: error: integer value 300 cannot be coerced to type 'u8'", "tmp.zig:11:20: error: expected type 'u8', found 'u16'", + "tmp.zig:11:20: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values", + "tmp.zig:15:25: error: expected type 'u64', found 'i8'", + "tmp.zig:15:25: note: unsigned 64-bit int cannot represent all possible signed 8-bit values", ); cases.add( diff --git a/test/gen_h.zig b/test/gen_h.zig index 5979afd66a..93ccb21a64 100644 --- a/test/gen_h.zig +++ b/test/gen_h.zig @@ -10,9 +10,8 @@ pub fn addCases(cases: *tests.GenHContext) void { \\ B = 1, \\ C = 2 \\}; - \\ - \\TEST_EXTERN_C void entry(enum Foo foo); - \\ + , + \\void entry(enum Foo foo); ); cases.add("declare struct", @@ -34,8 +33,8 @@ pub fn addCases(cases: *tests.GenHContext) void { \\ uint64_t E; \\ uint64_t F; \\}; - \\ - \\TEST_EXTERN_C void entry(struct Foo foo); + , + \\void entry(struct Foo foo); \\ ); @@ -69,19 +68,19 @@ pub fn addCases(cases: *tests.GenHContext) void { \\ bool C; \\ struct Big D; \\}; - \\ - \\TEST_EXTERN_C void entry(union Foo foo); + , + \\void entry(union Foo foo); \\ ); cases.add("declare opaque type", - \\export const Foo = @OpaqueType(); + \\const Foo = @OpaqueType(); \\ \\export fn entry(foo: ?*Foo) void { } , \\struct Foo; - \\ - \\TEST_EXTERN_C void entry(struct Foo * foo); + , + \\void entry(struct Foo * foo); ); cases.add("array field-type", @@ -95,8 +94,8 @@ pub fn addCases(cases: *tests.GenHContext) void { \\ int32_t A[2]; \\ uint32_t * B[4]; \\}; - \\ - \\TEST_EXTERN_C void entry(struct Foo foo, uint8_t bar[]); + , + \\void entry(struct Foo foo, uint8_t bar[]); \\ ); @@ -110,7 +109,8 @@ pub fn addCases(cases: *tests.GenHContext) void { \\} , \\struct S; - \\TEST_EXTERN_C uint8_t a(struct S * s); + , + \\uint8_t a(struct S * s); \\ ); @@ -125,7 +125,8 @@ pub fn addCases(cases: *tests.GenHContext) void { \\} , \\union U; - \\TEST_EXTERN_C uint8_t a(union U * s); + , + \\uint8_t a(union U * s); \\ ); @@ -140,7 +141,8 @@ pub fn addCases(cases: *tests.GenHContext) void { \\} , \\enum E; - \\TEST_EXTERN_C uint8_t a(enum E * s); + , + \\uint8_t a(enum E * s); \\ ); } diff --git a/test/standalone/cat/main.zig b/test/standalone/cat/main.zig index 7c15c15e53..f22329050c 100644 --- a/test/standalone/cat/main.zig +++ b/test/standalone/cat/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); const io = std.io; const process = std.process; -const File = std.fs.File; +const fs = std.fs; const mem = std.mem; const warn = std.debug.warn; const allocator = std.debug.global_allocator; @@ -12,6 +12,8 @@ pub fn main() !void { var catted_anything = false; const stdout_file = io.getStdOut(); + const cwd = fs.cwd(); + while (args_it.next(allocator)) |arg_or_err| { const arg = try unwrapArg(arg_or_err); if (mem.eql(u8, arg, "-")) { @@ -20,7 +22,7 @@ pub fn main() !void { } else if (arg[0] == '-') { return usage(exe); } else { - const file = File.openRead(arg) catch |err| { + const file = cwd.openFile(arg, .{}) catch |err| { warn("Unable to open file: {}\n", @errorName(err)); return err; }; @@ -40,7 +42,7 @@ fn usage(exe: []const u8) !void { return error.Invalid; } -fn cat_file(stdout: File, file: File) !void { +fn cat_file(stdout: fs.File, file: fs.File) !void { var buf: [1024 * 4]u8 = undefined; while (true) { diff --git a/test/standalone/static_c_lib/foo.c b/test/standalone/static_c_lib/foo.c index 2366282d47..78a332a6ee 100644 --- a/test/standalone/static_c_lib/foo.c +++ b/test/standalone/static_c_lib/foo.c @@ -2,3 +2,5 @@ uint32_t add(uint32_t a, uint32_t b) { return a + b; } + +uint32_t foo = 12345; diff --git a/test/standalone/static_c_lib/foo.h b/test/standalone/static_c_lib/foo.h index e8ebb9842e..6700499fe8 100644 --- a/test/standalone/static_c_lib/foo.h +++ b/test/standalone/static_c_lib/foo.h @@ -1,2 +1,3 @@ #include uint32_t add(uint32_t a, uint32_t b); +extern uint32_t foo; diff --git a/test/standalone/static_c_lib/foo.zig b/test/standalone/static_c_lib/foo.zig index edf94539c8..a5ba90c95d 100644 --- a/test/standalone/static_c_lib/foo.zig +++ b/test/standalone/static_c_lib/foo.zig @@ -6,3 +6,7 @@ test "C add" { const result = c.add(1, 2); expect(result == 3); } + +test "C extern variable" { + expect(c.foo == 12345); +} diff --git a/test/tests.zig b/test/tests.zig index 513c960f95..f908b889f2 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -70,6 +70,26 @@ const test_targets = [_]TestTarget{ .link_libc = true, }, + TestTarget{ + .target = Target{ + .Cross = CrossTarget{ + .os = .linux, + .arch = .i386, + .abi = .none, + }, + }, + }, + TestTarget{ + .target = Target{ + .Cross = CrossTarget{ + .os = .linux, + .arch = .i386, + .abi = .musl, + }, + }, + .link_libc = true, + }, + TestTarget{ .target = Target{ .Cross = CrossTarget{