diff --git a/README.md b/README.md index 92ad435f5..3312fa365 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ brew install cmake llvm@8 brew outdated llvm@8 || brew upgrade llvm@8 mkdir build cd build -cmake .. -DCMAKE_PREFIX_PATH=/usr/local/Cellar/llvm/8.0.0 +cmake .. -DCMAKE_PREFIX_PATH=/usr/local/Cellar/llvm/8.0.0_1 make install ``` diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 5171b80dd..bd20442d4 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -160,7 +160,7 @@ pub const Compilation = struct { /// it uses an optional pointer so that tombstone removals are possible fn_link_set: event.Locked(FnLinkSet), - pub const FnLinkSet = std.LinkedList(?*Value.Fn); + pub const FnLinkSet = std.TailQueue(?*Value.Fn); windows_subsystem_windows: bool, windows_subsystem_console: bool, diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 328646d2c..6908307c5 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -186,7 +186,7 @@ pub const Value = struct { /// Path to the object file that contains this function containing_object: Buffer, - link_set_node: *std.LinkedList(?*Value.Fn).Node, + link_set_node: *std.TailQueue(?*Value.Fn).Node, /// Creates a Fn value with 1 ref /// Takes ownership of symbol_name diff --git a/src/parser.cpp b/src/parser.cpp index 179c241fd..f35e54f6d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -890,6 +890,11 @@ static AstNode *ast_parse_if_statement(ParseContext *pc) { body = ast_parse_assign_expr(pc); } + if (body == nullptr) { + Token *tok = eat_token(pc); + ast_error(pc, tok, "expected if body, found '%s'", token_name(tok->id)); + } + Token *err_payload = nullptr; AstNode *else_body = nullptr; if (eat_token_if(pc, TokenIdKeywordElse) != nullptr) { @@ -994,6 +999,11 @@ static AstNode *ast_parse_for_statement(ParseContext *pc) { body = ast_parse_assign_expr(pc); } + if (body == nullptr) { + Token *tok = eat_token(pc); + ast_error(pc, tok, "expected loop body, found '%s'", token_name(tok->id)); + } + AstNode *else_body = nullptr; if (eat_token_if(pc, TokenIdKeywordElse) != nullptr) { else_body = ast_expect(pc, ast_parse_statement); @@ -1023,6 +1033,11 @@ static AstNode *ast_parse_while_statement(ParseContext *pc) { body = ast_parse_assign_expr(pc); } + if (body == nullptr) { + Token *tok = eat_token(pc); + ast_error(pc, tok, "expected loop body, found '%s'", token_name(tok->id)); + } + Token *err_payload = nullptr; AstNode *else_body = nullptr; if (eat_token_if(pc, TokenIdKeywordElse) != nullptr) { diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig index bf5700c51..e8d03c4f1 100644 --- a/std/atomic/queue.zig +++ b/std/atomic/queue.zig @@ -14,7 +14,7 @@ pub fn Queue(comptime T: type) type { mutex: std.Mutex, pub const Self = @This(); - pub const Node = std.LinkedList(T).Node; + pub const Node = std.TailQueue(T).Node; pub fn init() Self { return Self{ diff --git a/std/child_process.zig b/std/child_process.zig index 2a2a294e9..8e4c086d1 100644 --- a/std/child_process.zig +++ b/std/child_process.zig @@ -13,7 +13,7 @@ const BufMap = std.BufMap; const Buffer = std.Buffer; const builtin = @import("builtin"); const Os = builtin.Os; -const LinkedList = std.LinkedList; +const TailQueue = std.TailQueue; const maxInt = std.math.maxInt; pub const ChildProcess = struct { @@ -48,7 +48,7 @@ pub const ChildProcess = struct { pub cwd: ?[]const u8, err_pipe: if (os.windows.is_the_target) void else [2]os.fd_t, - llnode: if (os.windows.is_the_target) void else LinkedList(*ChildProcess).Node, + llnode: if (os.windows.is_the_target) void else TailQueue(*ChildProcess).Node, pub const SpawnError = error{OutOfMemory} || os.ExecveError || os.SetIdError || os.ChangeCurDirError || windows.CreateProcessError; @@ -388,7 +388,7 @@ pub const ChildProcess = struct { self.pid = pid; self.err_pipe = err_pipe; - self.llnode = LinkedList(*ChildProcess).Node.init(self); + self.llnode = TailQueue(*ChildProcess).Node.init(self); self.term = null; if (self.stdin_behavior == StdIo.Pipe) { diff --git a/std/debug.zig b/std/debug.zig index 62f99cacc..253f25071 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -2224,8 +2224,9 @@ fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit { fn readIntMem(ptr: *[*]const u8, comptime T: type, endian: builtin.Endian) T { // TODO https://github.com/ziglang/zig/issues/863 - const result = mem.readIntSlice(T, ptr.*[0..@sizeOf(T)], endian); - ptr.* += @sizeOf(T); + const size = (T.bit_count + 7) / 8; + const result = mem.readIntSlice(T, ptr.*[0..size], endian); + ptr.* += size; return result; } diff --git a/std/event/fs.zig b/std/event/fs.zig index 7a6a5861b..b48f8723c 100644 --- a/std/event/fs.zig +++ b/std/event/fs.zig @@ -887,7 +887,7 @@ pub fn Watch(comptime V: type) type { } async fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V { - const resolved_path = try std.fs.path.resolve(self.channel.loop.allocator, [][]const u8{file_path}); + const resolved_path = try std.fs.path.resolve(self.channel.loop.allocator, [_][]const u8{file_path}); var resolved_path_consumed = false; defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path); diff --git a/std/event/net.zig b/std/event/net.zig index f4398196e..413bf1432 100644 --- a/std/event/net.zig +++ b/std/event/net.zig @@ -19,7 +19,7 @@ pub const Server = struct { waiting_for_emfile_node: PromiseNode, listen_resume_node: event.Loop.ResumeNode, - const PromiseNode = std.LinkedList(promise).Node; + const PromiseNode = std.TailQueue(promise).Node; pub fn init(loop: *Loop) Server { // TODO can't initialize handler coroutine here because we need well defined copy elision diff --git a/std/hash_map.zig b/std/hash_map.zig index ab08d44cc..733681795 100644 --- a/std/hash_map.zig +++ b/std/hash_map.zig @@ -157,8 +157,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 fn ensureCapacityExact(self: *Self, new_capacity: usize) !void { // capacity must always be a power of two to allow for modulo // optimization in the constrainIndex fn - const is_power_of_two = new_capacity & (new_capacity - 1) == 0; - assert(is_power_of_two); + assert(math.isPowerOfTwo(new_capacity)); if (new_capacity <= self.entries.len) { return; diff --git a/std/heap.zig b/std/heap.zig index 7d7774f45..b35abd138 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -347,10 +347,10 @@ pub const ArenaAllocator = struct { pub allocator: Allocator, child_allocator: *Allocator, - buffer_list: std.LinkedList([]u8), + buffer_list: std.SinglyLinkedList([]u8), end_index: usize, - const BufNode = std.LinkedList([]u8).Node; + const BufNode = std.SinglyLinkedList([]u8).Node; pub fn init(child_allocator: *Allocator) ArenaAllocator { return ArenaAllocator{ @@ -359,7 +359,7 @@ pub const ArenaAllocator = struct { .shrinkFn = shrink, }, .child_allocator = child_allocator, - .buffer_list = std.LinkedList([]u8).init(), + .buffer_list = std.SinglyLinkedList([]u8).init(), .end_index = 0, }; } @@ -387,10 +387,9 @@ pub const ArenaAllocator = struct { const buf_node = &buf_node_slice[0]; buf_node.* = BufNode{ .data = buf, - .prev = null, .next = null, }; - self.buffer_list.append(buf_node); + self.buffer_list.prepend(buf_node); self.end_index = 0; return buf_node; } @@ -398,7 +397,7 @@ pub const ArenaAllocator = struct { fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 { const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator); - var cur_node = if (self.buffer_list.last) |last_node| last_node else try self.createNode(0, n + alignment); + var cur_node = if (self.buffer_list.first) |first_node| first_node else try self.createNode(0, n + alignment); while (true) { const cur_buf = cur_node.data[@sizeOf(BufNode)..]; const addr = @ptrToInt(cur_buf.ptr) + self.end_index; diff --git a/std/io.zig b/std/io.zig index de33d9562..a02fb56e2 100644 --- a/std/io.zig +++ b/std/io.zig @@ -164,32 +164,32 @@ pub fn InStream(comptime ReadError: type) type { /// Reads a native-endian integer pub fn readIntNative(self: *Self, comptime T: type) !T { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; try self.readNoEof(bytes[0..]); return mem.readIntNative(T, &bytes); } /// Reads a foreign-endian integer pub fn readIntForeign(self: *Self, comptime T: type) !T { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; try self.readNoEof(bytes[0..]); return mem.readIntForeign(T, &bytes); } pub fn readIntLittle(self: *Self, comptime T: type) !T { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; try self.readNoEof(bytes[0..]); return mem.readIntLittle(T, &bytes); } pub fn readIntBig(self: *Self, comptime T: type) !T { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; try self.readNoEof(bytes[0..]); return mem.readIntBig(T, &bytes); } pub fn readInt(self: *Self, comptime T: type, endian: builtin.Endian) !T { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; try self.readNoEof(bytes[0..]); return mem.readInt(T, &bytes, endian); } @@ -249,32 +249,32 @@ pub fn OutStream(comptime WriteError: type) type { /// Write a native-endian integer. pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; mem.writeIntNative(T, &bytes, value); return self.writeFn(self, bytes); } /// Write a foreign-endian integer. pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; mem.writeIntForeign(T, &bytes, value); return self.writeFn(self, bytes); } pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; mem.writeIntLittle(T, &bytes, value); return self.writeFn(self, bytes); } pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; mem.writeIntBig(T, &bytes, value); return self.writeFn(self, bytes); } pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void { - var bytes: [@sizeOf(T)]u8 = undefined; + var bytes: [(T.bit_count + 7 )/ 8]u8 = undefined; mem.writeInt(T, &bytes, value, endian); return self.writeFn(self, bytes); } diff --git a/std/linked_list.zig b/std/linked_list.zig index c4ad52591..649565315 100644 --- a/std/linked_list.zig +++ b/std/linked_list.zig @@ -5,8 +5,192 @@ const testing = std.testing; const mem = std.mem; const Allocator = mem.Allocator; -/// Generic doubly linked list. -pub fn LinkedList(comptime T: type) type { +/// A singly-linked list is headed by a single forward pointer. The elements +/// are singly linked for minimum space and pointer manipulation overhead at +/// the expense of O(n) removal for arbitrary elements. New elements can be +/// added to the list after an existing element or at the head of the list. +/// A singly-linked list may only be traversed in the forward direction. +/// Singly-linked lists are ideal for applications with large datasets and +/// few or no removals or for implementing a LIFO queue. +pub fn SinglyLinkedList(comptime T: type) type { + return struct { + const Self = @This(); + + /// Node inside the linked list wrapping the actual data. + pub const Node = struct { + next: ?*Node, + data: T, + + pub fn init(data: T) Node { + return Node{ + .next = null, + .data = data, + }; + } + + /// Insert a new node after the current one. + /// + /// Arguments: + /// new_node: Pointer to the new node to insert. + pub fn insertAfter(node: *Node, new_node: *Node) void { + new_node.next = node.next; + node.next = new_node; + } + + /// Remove a node from the list. + /// + /// Arguments: + /// node: Pointer to the node to be removed. + /// Returns: + /// node removed + pub fn removeNext(node: *Node) ?*Node { + const next_node = node.next orelse return null; + node.next = next_node.next; + return next_node; + } + }; + + first: ?*Node, + + /// Initialize a linked list. + /// + /// Returns: + /// An empty linked list. + pub fn init() Self { + return Self{ + .first = null, + }; + } + + /// Insert a new node after an existing one. + /// + /// Arguments: + /// node: Pointer to a node in the list. + /// new_node: Pointer to the new node to insert. + pub fn insertAfter(list: *Self, node: *Node, new_node: *Node) void { + node.insertAfter(new_node); + } + + /// Insert a new node at the head. + /// + /// Arguments: + /// new_node: Pointer to the new node to insert. + pub fn prepend(list: *Self, new_node: *Node) void { + new_node.next = list.first; + list.first = new_node; + } + + /// Remove a node from the list. + /// + /// Arguments: + /// node: Pointer to the node to be removed. + pub fn remove(list: *Self, node: *Node) void { + if (list.first == node) { + list.first = node.next; + } else { + var current_elm = list.first.?; + while (current_elm.next != node) { + current_elm = current_elm.next.?; + } + current_elm.next = node.next; + } + } + + /// Remove and return the first node in the list. + /// + /// Returns: + /// A pointer to the first node in the list. + pub fn popFirst(list: *Self) ?*Node { + const first = list.first orelse return null; + list.first = first.next; + return first; + } + + /// Allocate a new node. + /// + /// Arguments: + /// allocator: Dynamic memory allocator. + /// + /// Returns: + /// A pointer to the new node. + pub fn allocateNode(list: *Self, allocator: *Allocator) !*Node { + return allocator.create(Node); + } + + /// Deallocate a node. + /// + /// Arguments: + /// node: Pointer to the node to deallocate. + /// allocator: Dynamic memory allocator. + pub fn destroyNode(list: *Self, node: *Node, allocator: *Allocator) void { + allocator.destroy(node); + } + + /// Allocate and initialize a node and its data. + /// + /// Arguments: + /// data: The data to put inside the node. + /// allocator: Dynamic memory allocator. + /// + /// Returns: + /// A pointer to the new node. + pub fn createNode(list: *Self, data: T, allocator: *Allocator) !*Node { + var node = try list.allocateNode(allocator); + node.* = Node.init(data); + return node; + } + }; +} + +test "basic SinglyLinkedList test" { + const allocator = debug.global_allocator; + var list = SinglyLinkedList(u32).init(); + + var one = try list.createNode(1, allocator); + var two = try list.createNode(2, allocator); + var three = try list.createNode(3, allocator); + var four = try list.createNode(4, allocator); + var five = try list.createNode(5, allocator); + defer { + list.destroyNode(one, allocator); + list.destroyNode(two, allocator); + list.destroyNode(three, allocator); + list.destroyNode(four, allocator); + list.destroyNode(five, allocator); + } + + list.prepend(two); // {2} + list.insertAfter(two, five); // {2, 5} + list.prepend(one); // {1, 2, 5} + list.insertAfter(two, three); // {1, 2, 3, 5} + list.insertAfter(three, four); // {1, 2, 3, 4, 5} + + // Traverse forwards. + { + var it = list.first; + var index: u32 = 1; + while (it) |node| : (it = node.next) { + testing.expect(node.data == index); + index += 1; + } + } + + _ = list.popFirst(); // {2, 3, 4, 5} + _ = list.remove(five); // {2, 3, 4} + _ = two.removeNext(); // {2, 4} + + testing.expect(list.first.?.data == 2); + testing.expect(list.first.?.next.?.data == 4); + testing.expect(list.first.?.next.?.next == null); +} + +/// A tail queue is headed by a pair of pointers, one to the head of the +/// list and the other to the tail of the list. The elements are doubly +/// linked so that an arbitrary element can be removed without a need to +/// traverse the list. New elements can be added to the list before or +/// after an existing element, at the head of the list, or at the end of +/// the list. A tail queue may be traversed in either direction. +pub fn TailQueue(comptime T: type) type { return struct { const Self = @This(); @@ -219,9 +403,9 @@ pub fn LinkedList(comptime T: type) type { }; } -test "basic linked list test" { +test "basic TailQueue test" { const allocator = debug.global_allocator; - var list = LinkedList(u32).init(); + var list = TailQueue(u32).init(); var one = try list.createNode(1, allocator); var two = try list.createNode(2, allocator); @@ -271,10 +455,10 @@ test "basic linked list test" { testing.expect(list.len == 2); } -test "linked list concatenation" { +test "TailQueue concatenation" { const allocator = debug.global_allocator; - var list1 = LinkedList(u32).init(); - var list2 = LinkedList(u32).init(); + var list1 = TailQueue(u32).init(); + var list2 = TailQueue(u32).init(); var one = try list1.createNode(1, allocator); defer list1.destroyNode(one, allocator); diff --git a/std/math.zig b/std/math.zig index 765df6280..96f38aca9 100644 --- a/std/math.zig +++ b/std/math.zig @@ -288,10 +288,8 @@ pub fn shl(comptime T: type, a: T, shift_amt: var) T { const abs_shift_amt = absCast(shift_amt); const casted_shift_amt = if (abs_shift_amt >= T.bit_count) return 0 else @intCast(Log2Int(T), abs_shift_amt); - if (@typeOf(shift_amt).is_signed) { - if (shift_amt >= 0) { - return a << casted_shift_amt; - } else { + if (@typeOf(shift_amt) == comptime_int or @typeOf(shift_amt).is_signed) { + if (shift_amt < 0) { return a >> casted_shift_amt; } } @@ -304,6 +302,10 @@ test "math.shl" { testing.expect(shl(u8, 0b11111111, usize(8)) == 0); testing.expect(shl(u8, 0b11111111, usize(9)) == 0); testing.expect(shl(u8, 0b11111111, isize(-2)) == 0b00111111); + testing.expect(shl(u8, 0b11111111, 3) == 0b11111000); + testing.expect(shl(u8, 0b11111111, 8) == 0); + testing.expect(shl(u8, 0b11111111, 9) == 0); + testing.expect(shl(u8, 0b11111111, -2) == 0b00111111); } /// Shifts right. Overflowed bits are truncated. @@ -312,7 +314,7 @@ pub fn shr(comptime T: type, a: T, shift_amt: var) T { const abs_shift_amt = absCast(shift_amt); const casted_shift_amt = if (abs_shift_amt >= T.bit_count) return 0 else @intCast(Log2Int(T), abs_shift_amt); - if (@typeOf(shift_amt).is_signed) { + if (@typeOf(shift_amt) == comptime_int or @typeOf(shift_amt).is_signed) { if (shift_amt >= 0) { return a >> casted_shift_amt; } else { @@ -328,6 +330,10 @@ test "math.shr" { testing.expect(shr(u8, 0b11111111, usize(8)) == 0); testing.expect(shr(u8, 0b11111111, usize(9)) == 0); testing.expect(shr(u8, 0b11111111, isize(-2)) == 0b11111100); + testing.expect(shr(u8, 0b11111111, 3) == 0b00011111); + testing.expect(shr(u8, 0b11111111, 8) == 0); + testing.expect(shr(u8, 0b11111111, 9) == 0); + testing.expect(shr(u8, 0b11111111, -2) == 0b11111100); } /// Rotates right. Only unsigned values can be rotated. @@ -680,6 +686,11 @@ pub fn alignCast(comptime alignment: u29, ptr: var) AlignCastError!@typeOf(@alig return @alignCast(alignment, ptr); } +pub fn isPowerOfTwo(v: var) bool { + assert(v != 0); + return (v & (v - 1)) == 0; +} + pub fn floorPowerOfTwo(comptime T: type, value: T) T { var x = value; diff --git a/std/math/big/int.zig b/std/math/big/int.zig index adc22ffa3..46b1bed9a 100644 --- a/std/math/big/int.zig +++ b/std/math/big/int.zig @@ -447,7 +447,7 @@ pub const Int = struct { } // Power of two: can do a single pass and use masks to extract digits. - if (base & (base - 1) == 0) { + if (math.isPowerOfTwo(base)) { const base_shift = math.log2_int(Limb, base); for (self.limbs[0..self.len()]) |limb| { diff --git a/std/os/bits/linux.zig b/std/os/bits/linux.zig index 8b323fa57..0b355cd81 100644 --- a/std/os/bits/linux.zig +++ b/std/os/bits/linux.zig @@ -119,6 +119,23 @@ pub const O_RDONLY = 0o0; pub const O_WRONLY = 0o1; pub const O_RDWR = 0o2; +pub const kernel_rwf = u32; + +/// high priority request, poll if possible +pub const RWF_HIPRI = kernel_rwf(0x00000001); + +/// per-IO O_DSYNC +pub const RWF_DSYNC = kernel_rwf(0x00000002); + +/// per-IO O_SYNC +pub const RWF_SYNC = kernel_rwf(0x00000004); + +/// per-IO, return -EAGAIN if operation would block +pub const RWF_NOWAIT = kernel_rwf(0x00000008); + +/// per-IO O_APPEND +pub const RWF_APPEND = kernel_rwf(0x00000010); + pub const SEEK_SET = 0; pub const SEEK_CUR = 1; pub const SEEK_END = 2; @@ -950,3 +967,129 @@ pub const stack_t = extern struct { ss_flags: i32, ss_size: isize, }; + +pub const io_uring_params = extern struct { + sq_entries: u32, + cq_entries: u32, + flags: u32, + sq_thread_cpu: u32, + sq_thread_idle: u32, + resv: [5]u32, + sq_off: io_sqring_offsets, + cq_off: io_cqring_offsets, +}; + +// io_uring_params.flags + +/// io_context is polled +pub const IORING_SETUP_IOPOLL = (1 << 0); + +/// SQ poll thread +pub const IORING_SETUP_SQPOLL = (1 << 1); + +/// sq_thread_cpu is valid +pub const IORING_SETUP_SQ_AFF = (1 << 2); + +pub const io_sqring_offsets = extern struct { + /// offset of ring head + head: u32, + + /// offset of ring tail + tail: u32, + + /// ring mask value + ring_mask: u32, + + /// entries in ring + ring_entries: u32, + + /// ring flags + flags: u32, + + /// number of sqes not submitted + dropped: u32, + + /// sqe index array + array: u32, + + resv1: u32, + resv2: u64, +}; + +// io_sqring_offsets.flags + +/// needs io_uring_enter wakeup +pub const IORING_SQ_NEED_WAKEUP = 1 << 0; + +pub const io_cqring_offsets = extern struct { + head: u32, + tail: u32, + ring_mask: u32, + ring_entries: u32, + overflow: u32, + cqes: u32, + resv: [2]u64, +}; + +pub const io_uring_sqe = extern struct { + opcode: u8, + flags: u8, + ioprio: u16, + fd: i32, + off: u64, + addr: u64, + len: u32, + pub const union1 = extern union { + rw_flags: kernel_rwf, + fsync_flags: u32, + poll_event: u16, + }; + union1: union1, + user_data: u64, + pub const union2 = extern union { + buf_index: u16, + __pad2: [3]u64, + }; + union2: union2, +}; + +// io_uring_sqe.flags + +/// use fixed fileset +pub const IOSQE_FIXED_FILE = (1 << 0); + +pub const IORING_OP_NOP = 0; +pub const IORING_OP_READV = 1; +pub const IORING_OP_WRITEV = 2; +pub const IORING_OP_FSYNC = 3; +pub const IORING_OP_READ_FIXED = 4; +pub const IORING_OP_WRITE_FIXED = 5; +pub const IORING_OP_POLL_ADD = 6; +pub const IORING_OP_POLL_REMOVE = 7; + +// io_uring_sqe.fsync_flags +pub const IORING_FSYNC_DATASYNC = (1 << 0); + +// IO completion data structure (Completion Queue Entry) +pub const io_uring_cqe = extern struct { + /// io_uring_sqe.data submission passed back + user_data: u64, + + /// result code for this event + res: i32, + flags: u32, +}; + +pub const IORING_OFF_SQ_RING = 0; +pub const IORING_OFF_CQ_RING = 0x8000000; +pub const IORING_OFF_SQES = 0x10000000; + +// io_uring_enter flags +pub const IORING_ENTER_GETEVENTS = (1 << 0); +pub const IORING_ENTER_SQ_WAKEUP = (1 << 1); + +// io_uring_register opcodes and arguments +pub const IORING_REGISTER_BUFFERS = 0; +pub const IORING_UNREGISTER_BUFFERS = 1; +pub const IORING_REGISTER_FILES = 2; +pub const IORING_UNREGISTER_FILES = 3; diff --git a/std/os/linux.zig b/std/os/linux.zig index 61a13ff16..74e1c4844 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -189,6 +189,10 @@ pub fn preadv(fd: i32, iov: [*]const iovec, count: usize, offset: u64) usize { return syscall4(SYS_preadv, @bitCast(usize, isize(fd)), @ptrToInt(iov), count, offset); } +pub fn preadv2(fd: i32, iov: [*]const iovec, count: usize, offset: u64, flags: kernel_rwf) usize { + return syscall5(SYS_preadv2, @bitCast(usize, isize(fd)), @ptrToInt(iov), count, offset, flags); +} + pub fn readv(fd: i32, iov: [*]const iovec, count: usize) usize { return syscall3(SYS_readv, @bitCast(usize, isize(fd)), @ptrToInt(iov), count); } @@ -201,6 +205,10 @@ pub fn pwritev(fd: i32, iov: [*]const iovec_const, count: usize, offset: u64) us return syscall4(SYS_pwritev, @bitCast(usize, isize(fd)), @ptrToInt(iov), count, offset); } +pub fn pwritev2(fd: i32, iov: [*]const iovec_const, count: usize, offset: u64, flags: kernel_rwf) usize { + return syscall5(SYS_pwritev2, @bitCast(usize, isize(fd)), @ptrToInt(iov), count, offset, flags); +} + // TODO https://github.com/ziglang/zig/issues/265 pub fn rmdir(path: [*]const u8) usize { if (@hasDecl(@This(), "SYS_rmdir")) { @@ -887,6 +895,18 @@ pub fn dl_iterate_phdr(comptime T: type, callback: extern fn (info: *dl_phdr_inf return last_r; } +pub fn io_uring_setup(entries: u32, p: *io_uring_params) usize { + return syscall2(SYS_io_uring_setup, entries, @ptrToInt(p)); +} + +pub fn io_uring_enter(fd: i32, to_submit: u32, min_complete: u32, flags: u32, sig: ?*sigset_t) usize { + return syscall6(SYS_io_uring_enter, @bitCast(usize, isize(fd)), to_submit, min_complete, flags, @ptrToInt(sig), NSIG / 8); +} + +pub fn io_uring_register(fd: i32, opcode: u32, arg: ?*const c_void, nr_args: u32) usize { + return syscall4(SYS_io_uring_register, @bitCast(usize, isize(fd)), opcode, @ptrToInt(arg), nr_args); +} + test "" { if (is_the_target) { _ = @import("linux/test.zig"); diff --git a/std/rand.zig b/std/rand.zig index 1c09bc693..e14a60e2a 100644 --- a/std/rand.zig +++ b/std/rand.zig @@ -18,6 +18,7 @@ const std = @import("std.zig"); const builtin = @import("builtin"); const assert = std.debug.assert; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; const mem = std.mem; const math = std.math; const ziggurat = @import("rand/ziggurat.zig"); @@ -935,6 +936,105 @@ test "isaac64 sequence" { } } +/// Sfc64 pseudo-random number generator from Practically Random. +/// Fastest engine of pracrand and smallest footprint. +/// See http://pracrand.sourceforge.net/ +pub const Sfc64 = struct { + random: Random, + + a: u64 = undefined, + b: u64 = undefined, + c: u64 = undefined, + counter: u64 = undefined, + + const Rotation = 24; + const RightShift = 11; + const LeftShift = 3; + + pub fn init(init_s: u64) Sfc64 { + var x = Sfc64{ + .random = Random{ .fillFn = fill }, + }; + + x.seed(init_s); + return x; + } + + fn next(self: *Sfc64) u64 { + const tmp = self.a +% self.b +% self.counter; + self.counter += 1; + self.a = self.b ^ (self.b >> RightShift); + self.b = self.c +% (self.c << LeftShift); + self.c = math.rotl(u64, self.c, Rotation) +% tmp; + return tmp; + } + + fn seed(self: *Sfc64, init_s: u64) void { + self.a = init_s; + self.b = init_s; + self.c = init_s; + self.counter = 1; + var i: u32 = 0; + while (i < 12) : (i += 1) { + _ = self.next(); + } + } + + fn fill(r: *Random, buf: []u8) void { + const self = @fieldParentPtr(Sfc64, "random", r); + + var i: usize = 0; + const aligned_len = buf.len - (buf.len & 7); + + // Complete 8 byte segments. + while (i < aligned_len) : (i += 8) { + var n = self.next(); + comptime var j: usize = 0; + inline while (j < 8) : (j += 1) { + buf[i + j] = @truncate(u8, n); + n >>= 8; + } + } + + // Remaining. (cuts the stream) + if (i != buf.len) { + var n = self.next(); + while (i < buf.len) : (i += 1) { + buf[i] = @truncate(u8, n); + n >>= 8; + } + } + } +}; + +test "Sfc64 sequence" { + // Unfortunately there does not seem to be an official test sequence. + var r = Sfc64.init(0); + + const seq = [_]u64{ + 0x3acfa029e3cc6041, + 0xf5b6515bf2ee419c, + 0x1259635894a29b61, + 0xb6ae75395f8ebd6, + 0x225622285ce302e2, + 0x520d28611395cb21, + 0xdb909c818901599d, + 0x8ffd195365216f57, + 0xe8c4ad5e258ac04a, + 0x8f8ef2c89fdb63ca, + 0xf9865b01d98d8e2f, + 0x46555871a65d08ba, + 0x66868677c6298fcd, + 0x2ce15a7e6329f57d, + 0xb2f1833ca91ca79, + 0x4b0890ac9bf453ca, + }; + + for (seq) |s| { + expectEqual(s, r.next()); + } +} + // Actual Random helper function tests, pcg engine is assumed correct. test "Random float" { var prng = DefaultPrng.init(0); diff --git a/std/segmented_list.zig b/std/segmented_list.zig index b3a7065ad..dc3358cd8 100644 --- a/std/segmented_list.zig +++ b/std/segmented_list.zig @@ -80,9 +80,9 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type const prealloc_exp = blk: { // we don't use the prealloc_exp constant when prealloc_item_count is 0. assert(prealloc_item_count != 0); + assert(std.math.isPowerOfTwo(prealloc_item_count)); const value = std.math.log2_int(usize, prealloc_item_count); - assert((1 << value) == prealloc_item_count); // prealloc_item_count must be a power of 2 break :blk @typeOf(1)(value); }; const ShelfIndex = std.math.Log2Int(usize); diff --git a/std/std.zig b/std/std.zig index a621af1e8..603cb1092 100644 --- a/std/std.zig +++ b/std/std.zig @@ -7,17 +7,18 @@ pub const Buffer = @import("buffer.zig").Buffer; pub const BufferOutStream = @import("io.zig").BufferOutStream; pub const DynLib = @import("dynamic_library.zig").DynLib; pub const HashMap = @import("hash_map.zig").HashMap; -pub const LinkedList = @import("linked_list.zig").LinkedList; pub const Mutex = @import("mutex.zig").Mutex; pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; +pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig").StaticallyInitializedMutex; pub const SegmentedList = @import("segmented_list.zig").SegmentedList; pub const SpinLock = @import("spinlock.zig").SpinLock; pub const ChildProcess = @import("child_process.zig").ChildProcess; +pub const TailQueue = @import("linked_list.zig").TailQueue; pub const Thread = @import("thread.zig").Thread; pub const atomic = @import("atomic.zig"); diff --git a/std/testing.zig b/std/testing.zig index f4b10a377..4568e024e 100644 --- a/std/testing.zig +++ b/std/testing.zig @@ -78,8 +78,10 @@ pub fn expectEqual(expected: var, actual: @typeOf(expected)) void { TypeId.Array => |array| expectEqualSlices(array.child, &expected, &actual), - TypeId.Struct => { - @compileError("TODO implement testing.expectEqual for structs"); + TypeId.Struct => |structType| { + inline for (structType.fields) |field| { + expectEqual(@field(expected, field.name), @field(actual, field.name)); + } }, TypeId.Union => |union_info| { diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 15ac7d6f1..7896a0b73 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -230,6 +230,33 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:10:25: error: expression value is ignored", ); + cases.add( + "empty while loop body", + \\export fn a() void { + \\ while(true); + \\} + , + "tmp.zig:2:16: error: expected loop body, found ';'", + ); + + cases.add( + "empty for loop body", + \\export fn a() void { + \\ for(undefined) |x|; + \\} + , + "tmp.zig:2:23: error: expected loop body, found ';'", + ); + + cases.add( + "empty if body", + \\export fn a() void { + \\ if(true); + \\} + , + "tmp.zig:2:13: error: expected if body, found ';'", + ); + cases.add( "import outside package path", \\comptime{