From ed41d10a068065ea80f93736926d9c3240d62c49 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Sat, 4 May 2019 13:54:28 +1000 Subject: [PATCH 1/3] std: existing LinkedList is actually a TailQueue --- src-self-hosted/compilation.zig | 2 +- src-self-hosted/value.zig | 2 +- std/atomic/queue.zig | 2 +- std/child_process.zig | 6 +++--- std/event/net.zig | 2 +- std/heap.zig | 6 +++--- std/linked_list.zig | 19 ++++++++++++------- std/std.zig | 2 +- 8 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 5171b80ddc..bd20442d4b 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 328646d2cd..6908307c56 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/std/atomic/queue.zig b/std/atomic/queue.zig index bf5700c51e..e8d03c4f13 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 2a2a294e9b..8e4c086d1d 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/event/net.zig b/std/event/net.zig index f4398196e3..413bf1432c 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/heap.zig b/std/heap.zig index 7d7774f453..ba2b4c816c 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.TailQueue([]u8), end_index: usize, - const BufNode = std.LinkedList([]u8).Node; + const BufNode = std.TailQueue([]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.TailQueue([]u8).init(), .end_index = 0, }; } diff --git a/std/linked_list.zig b/std/linked_list.zig index c4ad525913..65fbd707f5 100644 --- a/std/linked_list.zig +++ b/std/linked_list.zig @@ -5,8 +5,13 @@ const testing = std.testing; const mem = std.mem; const Allocator = mem.Allocator; -/// Generic doubly linked list. -pub fn LinkedList(comptime T: type) type { +/// 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 +224,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 +276,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/std.zig b/std/std.zig index a621af1e84..d429ca8a21 100644 --- a/std/std.zig +++ b/std/std.zig @@ -7,7 +7,6 @@ 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; @@ -18,6 +17,7 @@ pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig 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"); From ddf7942aaa6a41296d9338423dcdfb93b915e4df Mon Sep 17 00:00:00 2001 From: daurnimator Date: Sat, 4 May 2019 13:50:37 +1000 Subject: [PATCH 2/3] std: Add singly linked list --- std/linked_list.zig | 179 ++++++++++++++++++++++++++++++++++++++++++++ std/std.zig | 1 + 2 files changed, 180 insertions(+) diff --git a/std/linked_list.zig b/std/linked_list.zig index 65fbd707f5..6495653150 100644 --- a/std/linked_list.zig +++ b/std/linked_list.zig @@ -5,6 +5,185 @@ const testing = std.testing; const mem = std.mem; const Allocator = mem.Allocator; +/// 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 diff --git a/std/std.zig b/std/std.zig index d429ca8a21..603cb10929 100644 --- a/std/std.zig +++ b/std/std.zig @@ -13,6 +13,7 @@ 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; From b0648bfbd31cad73e00ee72b430dcf2c2907e08f Mon Sep 17 00:00:00 2001 From: daurnimator Date: Sat, 4 May 2019 14:02:04 +1000 Subject: [PATCH 3/3] std.heap.ArenaAllocator: use singly linked list --- std/heap.zig | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/std/heap.zig b/std/heap.zig index ba2b4c816c..b35abd138c 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.TailQueue([]u8), + buffer_list: std.SinglyLinkedList([]u8), end_index: usize, - const BufNode = std.TailQueue([]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.TailQueue([]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;