| author | |
| committer | |
| log | bfda12efcf2f6b4bc6803f520108b7ce05636965 |
| tree | 99133f597a833d32522c42aba84782682f989c9b |
| parent | a50c2a4eae66b772386c62e83f48007e98f9160c |
| parent | 6d4dbf05effa3afeb650aeea17683d5de4e6429c |
| signature |
std: add a Deque data structure4 files changed, 455 insertions(+), 191 deletions(-)
lib/std/deque.zig created+433| ... | @@ -0,0 +1,433 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const Allocator = std.mem.Allocator; | ||
| 4 | |||
| 5 | /// A contiguous, growable, double-ended queue. | ||
| 6 | /// | ||
| 7 | /// Pushing/popping items from either end of the queue is O(1). | ||
| 8 | pub fn Deque(comptime T: type) type { | ||
| 9 | return struct { | ||
| 10 | const Self = @This(); | ||
| 11 | |||
| 12 | /// A ring buffer. | ||
| 13 | buffer: []T, | ||
| 14 | /// The index in buffer where the first item in the logical deque is stored. | ||
| 15 | head: usize, | ||
| 16 | /// The number of items stored in the logical deque. | ||
| 17 | len: usize, | ||
| 18 | |||
| 19 | /// A Deque containing no elements. | ||
| 20 | pub const empty: Self = .{ | ||
| 21 | .buffer = &.{}, | ||
| 22 | .head = 0, | ||
| 23 | .len = 0, | ||
| 24 | }; | ||
| 25 | |||
| 26 | /// Initialize with capacity to hold `capacity` elements. | ||
| 27 | /// The resulting capacity will equal `capacity` exactly. | ||
| 28 | /// Deinitialize with `deinit`. | ||
| 29 | pub fn initCapacity(gpa: Allocator, capacity: usize) Allocator.Error!Self { | ||
| 30 | var deque: Self = .empty; | ||
| 31 | try deque.ensureTotalCapacityPrecise(gpa, capacity); | ||
| 32 | return deque; | ||
| 33 | } | ||
| 34 | |||
| 35 | /// Initialize with externally-managed memory. The buffer determines the | ||
| 36 | /// capacity and the deque is initially empty. | ||
| 37 | /// | ||
| 38 | /// When initialized this way, all functions that accept an Allocator | ||
| 39 | /// argument cause illegal behavior. | ||
| 40 | pub fn initBuffer(buffer: []T) Self { | ||
| 41 | return .{ | ||
| 42 | .buffer = buffer, | ||
| 43 | .head = 0, | ||
| 44 | .len = 0, | ||
| 45 | }; | ||
| 46 | } | ||
| 47 | |||
| 48 | /// Release all allocated memory. | ||
| 49 | pub fn deinit(deque: *Self, gpa: Allocator) void { | ||
| 50 | gpa.free(deque.buffer); | ||
| 51 | deque.* = undefined; | ||
| 52 | } | ||
| 53 | |||
| 54 | /// Modify the deque so that it can hold at least `new_capacity` items. | ||
| 55 | /// Implements super-linear growth to achieve amortized O(1) push/pop operations. | ||
| 56 | /// Invalidates element pointers if additional memory is needed. | ||
| 57 | pub fn ensureTotalCapacity(deque: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void { | ||
| 58 | if (deque.buffer.len >= new_capacity) return; | ||
| 59 | return deque.ensureTotalCapacityPrecise(gpa, growCapacity(deque.buffer.len, new_capacity)); | ||
| 60 | } | ||
| 61 | |||
| 62 | /// If the current capacity is less than `new_capacity`, this function will | ||
| 63 | /// modify the deque so that it can hold exactly `new_capacity` items. | ||
| 64 | /// Invalidates element pointers if additional memory is needed. | ||
| 65 | pub fn ensureTotalCapacityPrecise(deque: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void { | ||
| 66 | if (deque.buffer.len >= new_capacity) return; | ||
| 67 | const old_buffer = deque.buffer; | ||
| 68 | if (gpa.remap(old_buffer, new_capacity)) |new_buffer| { | ||
| 69 | // If the items wrap around the end of the buffer we need to do | ||
| 70 | // a memcpy to prevent a gap after resizing the buffer. | ||
| 71 | if (deque.head > old_buffer.len - deque.len) { | ||
| 72 | // The gap splits the items in the deque into head and tail parts. | ||
| 73 | // Choose the shorter part to copy. | ||
| 74 | const head = new_buffer[deque.head..old_buffer.len]; | ||
| 75 | const tail = new_buffer[0 .. deque.len - head.len]; | ||
| 76 | if (head.len > tail.len and new_buffer.len - old_buffer.len > tail.len) { | ||
| 77 | @memcpy(new_buffer[old_buffer.len..][0..tail.len], tail); | ||
| 78 | } else { | ||
| 79 | // In this case overlap is possible if e.g. the capacity increase is 1 | ||
| 80 | // and head.len is greater than 1. | ||
| 81 | deque.head = new_buffer.len - head.len; | ||
| 82 | @memmove(new_buffer[deque.head..][0..head.len], head); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | deque.buffer = new_buffer; | ||
| 86 | } else { | ||
| 87 | const new_buffer = try gpa.alloc(T, new_capacity); | ||
| 88 | if (deque.head < old_buffer.len - deque.len) { | ||
| 89 | @memcpy(new_buffer[0..deque.len], old_buffer[deque.head..][0..deque.len]); | ||
| 90 | } else { | ||
| 91 | const head = old_buffer[deque.head..]; | ||
| 92 | const tail = old_buffer[0 .. deque.len - head.len]; | ||
| 93 | @memcpy(new_buffer[0..head.len], head); | ||
| 94 | @memcpy(new_buffer[head.len..][0..tail.len], tail); | ||
| 95 | } | ||
| 96 | deque.head = 0; | ||
| 97 | deque.buffer = new_buffer; | ||
| 98 | gpa.free(old_buffer); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | /// Modify the deque so that it can hold at least `additional_count` **more** items. | ||
| 103 | /// Invalidates element pointers if additional memory is needed. | ||
| 104 | pub fn ensureUnusedCapacity( | ||
| 105 | deque: *Self, | ||
| 106 | gpa: Allocator, | ||
| 107 | additional_count: usize, | ||
| 108 | ) Allocator.Error!void { | ||
| 109 | return deque.ensureTotalCapacity(gpa, try addOrOom(deque.len, additional_count)); | ||
| 110 | } | ||
| 111 | |||
| 112 | /// Add one item to the front of the deque. | ||
| 113 | /// | ||
| 114 | /// Invalidates element pointers if additional memory is needed. | ||
| 115 | pub fn pushFront(deque: *Self, gpa: Allocator, item: T) error{OutOfMemory}!void { | ||
| 116 | try deque.ensureUnusedCapacity(gpa, 1); | ||
| 117 | deque.pushFrontAssumeCapacity(item); | ||
| 118 | } | ||
| 119 | |||
| 120 | /// Add one item to the front of the deque. | ||
| 121 | /// | ||
| 122 | /// Never invalidates element pointers. | ||
| 123 | /// | ||
| 124 | /// If the deque lacks unused capacity for the additional item, returns | ||
| 125 | /// `error.OutOfMemory`. | ||
| 126 | pub fn pushFrontBounded(deque: *Self, item: T) error{OutOfMemory}!void { | ||
| 127 | if (deque.buffer.len - deque.len == 0) return error.OutOfMemory; | ||
| 128 | return deque.pushFrontAssumeCapacity(item); | ||
| 129 | } | ||
| 130 | |||
| 131 | /// Add one item to the front of the deque. | ||
| 132 | /// | ||
| 133 | /// Never invalidates element pointers. | ||
| 134 | /// | ||
| 135 | /// Asserts that the deque can hold one additional item. | ||
| 136 | pub fn pushFrontAssumeCapacity(deque: *Self, item: T) void { | ||
| 137 | assert(deque.len < deque.buffer.len); | ||
| 138 | if (deque.head == 0) { | ||
| 139 | deque.head = deque.buffer.len; | ||
| 140 | } | ||
| 141 | deque.head -= 1; | ||
| 142 | deque.buffer[deque.head] = item; | ||
| 143 | deque.len += 1; | ||
| 144 | } | ||
| 145 | |||
| 146 | /// Add one item to the back of the deque. | ||
| 147 | /// | ||
| 148 | /// Invalidates element pointers if additional memory is needed. | ||
| 149 | pub fn pushBack(deque: *Self, gpa: Allocator, item: T) error{OutOfMemory}!void { | ||
| 150 | try deque.ensureUnusedCapacity(gpa, 1); | ||
| 151 | deque.pushBackAssumeCapacity(item); | ||
| 152 | } | ||
| 153 | |||
| 154 | /// Add one item to the back of the deque. | ||
| 155 | /// | ||
| 156 | /// Never invalidates element pointers. | ||
| 157 | /// | ||
| 158 | /// If the deque lacks unused capacity for the additional item, returns | ||
| 159 | /// `error.OutOfMemory`. | ||
| 160 | pub fn pushBackBounded(deque: *Self, item: T) error{OutOfMemory}!void { | ||
| 161 | if (deque.buffer.len - deque.len == 0) return error.OutOfMemory; | ||
| 162 | deque.pushBackAssumeCapacity(item); | ||
| 163 | } | ||
| 164 | |||
| 165 | /// Add one item to the back of the deque. | ||
| 166 | /// | ||
| 167 | /// Never invalidates element pointers. | ||
| 168 | /// | ||
| 169 | /// Asserts that the deque can hold one additional item. | ||
| 170 | pub fn pushBackAssumeCapacity(deque: *Self, item: T) void { | ||
| 171 | assert(deque.len < deque.buffer.len); | ||
| 172 | const buffer_index = deque.bufferIndex(deque.len); | ||
| 173 | deque.buffer[buffer_index] = item; | ||
| 174 | deque.len += 1; | ||
| 175 | } | ||
| 176 | |||
| 177 | /// Return the first item in the deque or null if empty. | ||
| 178 | pub fn front(deque: *const Self) ?T { | ||
| 179 | if (deque.len == 0) return null; | ||
| 180 | return deque.buffer[deque.head]; | ||
| 181 | } | ||
| 182 | |||
| 183 | /// Return the last item in the deque or null if empty. | ||
| 184 | pub fn back(deque: *const Self) ?T { | ||
| 185 | if (deque.len == 0) return null; | ||
| 186 | return deque.buffer[deque.bufferIndex(deque.len - 1)]; | ||
| 187 | } | ||
| 188 | |||
| 189 | /// Return the item at the given index in the deque. | ||
| 190 | /// | ||
| 191 | /// The first item in the queue is at index 0. | ||
| 192 | /// | ||
| 193 | /// Asserts that the index is in-bounds. | ||
| 194 | pub fn at(deque: *const Self, index: usize) T { | ||
| 195 | assert(index < deque.len); | ||
| 196 | return deque.buffer[deque.bufferIndex(index)]; | ||
| 197 | } | ||
| 198 | |||
| 199 | /// Remove and return the first item in the deque or null if empty. | ||
| 200 | pub fn popFront(deque: *Self) ?T { | ||
| 201 | if (deque.len == 0) return null; | ||
| 202 | const pop_index = deque.head; | ||
| 203 | deque.head = deque.bufferIndex(1); | ||
| 204 | deque.len -= 1; | ||
| 205 | return deque.buffer[pop_index]; | ||
| 206 | } | ||
| 207 | |||
| 208 | /// Remove and return the last item in the deque or null if empty. | ||
| 209 | pub fn popBack(deque: *Self) ?T { | ||
| 210 | if (deque.len == 0) return null; | ||
| 211 | deque.len -= 1; | ||
| 212 | return deque.buffer[deque.bufferIndex(deque.len)]; | ||
| 213 | } | ||
| 214 | |||
| 215 | pub const Iterator = struct { | ||
| 216 | deque: *const Self, | ||
| 217 | index: usize, | ||
| 218 | |||
| 219 | pub fn next(it: *Iterator) ?T { | ||
| 220 | if (it.index < it.deque.len) { | ||
| 221 | defer it.index += 1; | ||
| 222 | return it.deque.at(it.index); | ||
| 223 | } else { | ||
| 224 | return null; | ||
| 225 | } | ||
| 226 | } | ||
| 227 | }; | ||
| 228 | |||
| 229 | /// Iterates over all items in the deque in order from front to back. | ||
| 230 | pub fn iterator(deque: *const Self) Iterator { | ||
| 231 | return .{ .deque = deque, .index = 0 }; | ||
| 232 | } | ||
| 233 | |||
| 234 | /// Returns the index in `buffer` where the element at the given | ||
| 235 | /// index in the logical deque is stored. | ||
| 236 | fn bufferIndex(deque: *const Self, index: usize) usize { | ||
| 237 | // This function is written in this way to avoid overflow and | ||
| 238 | // expensive division. | ||
| 239 | const head_len = deque.buffer.len - deque.head; | ||
| 240 | if (index < head_len) { | ||
| 241 | return deque.head + index; | ||
| 242 | } else { | ||
| 243 | return index - head_len; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | const init_capacity: comptime_int = @max(1, std.atomic.cache_line / @sizeOf(T)); | ||
| 248 | |||
| 249 | /// Called when memory growth is necessary. Returns a capacity larger than | ||
| 250 | /// minimum that grows super-linearly. | ||
| 251 | fn growCapacity(current: usize, minimum: usize) usize { | ||
| 252 | var new = current; | ||
| 253 | while (true) { | ||
| 254 | new +|= new / 2 + init_capacity; | ||
| 255 | if (new >= minimum) return new; | ||
| 256 | } | ||
| 257 | } | ||
| 258 | }; | ||
| 259 | } | ||
| 260 | |||
| 261 | /// Integer addition returning `error.OutOfMemory` on overflow. | ||
| 262 | fn addOrOom(a: usize, b: usize) error{OutOfMemory}!usize { | ||
| 263 | const result, const overflow = @addWithOverflow(a, b); | ||
| 264 | if (overflow != 0) return error.OutOfMemory; | ||
| 265 | return result; | ||
| 266 | } | ||
| 267 | |||
| 268 | test "basic" { | ||
| 269 | const testing = std.testing; | ||
| 270 | const gpa = testing.allocator; | ||
| 271 | |||
| 272 | var q: Deque(u32) = .empty; | ||
| 273 | defer q.deinit(gpa); | ||
| 274 | |||
| 275 | try testing.expectEqual(null, q.popFront()); | ||
| 276 | try testing.expectEqual(null, q.popBack()); | ||
| 277 | |||
| 278 | try q.pushBack(gpa, 1); | ||
| 279 | try q.pushBack(gpa, 2); | ||
| 280 | try q.pushBack(gpa, 3); | ||
| 281 | try q.pushFront(gpa, 0); | ||
| 282 | |||
| 283 | try testing.expectEqual(0, q.popFront()); | ||
| 284 | try testing.expectEqual(1, q.popFront()); | ||
| 285 | try testing.expectEqual(3, q.popBack()); | ||
| 286 | try testing.expectEqual(2, q.popFront()); | ||
| 287 | try testing.expectEqual(null, q.popFront()); | ||
| 288 | try testing.expectEqual(null, q.popBack()); | ||
| 289 | } | ||
| 290 | |||
| 291 | test "buffer" { | ||
| 292 | const testing = std.testing; | ||
| 293 | |||
| 294 | var buffer: [4]u32 = undefined; | ||
| 295 | var q: Deque(u32) = .initBuffer(&buffer); | ||
| 296 | |||
| 297 | try testing.expectEqual(null, q.popFront()); | ||
| 298 | try testing.expectEqual(null, q.popBack()); | ||
| 299 | |||
| 300 | try q.pushBackBounded(1); | ||
| 301 | try q.pushBackBounded(2); | ||
| 302 | try q.pushBackBounded(3); | ||
| 303 | try q.pushFrontBounded(0); | ||
| 304 | try testing.expectError(error.OutOfMemory, q.pushBackBounded(4)); | ||
| 305 | |||
| 306 | try testing.expectEqual(0, q.popFront()); | ||
| 307 | try testing.expectEqual(1, q.popFront()); | ||
| 308 | try testing.expectEqual(3, q.popBack()); | ||
| 309 | try testing.expectEqual(2, q.popFront()); | ||
| 310 | try testing.expectEqual(null, q.popFront()); | ||
| 311 | try testing.expectEqual(null, q.popBack()); | ||
| 312 | } | ||
| 313 | |||
| 314 | test "slow growth" { | ||
| 315 | const testing = std.testing; | ||
| 316 | const gpa = testing.allocator; | ||
| 317 | |||
| 318 | var q: Deque(i32) = .empty; | ||
| 319 | defer q.deinit(gpa); | ||
| 320 | |||
| 321 | try q.ensureTotalCapacityPrecise(gpa, 1); | ||
| 322 | q.pushBackAssumeCapacity(1); | ||
| 323 | try q.ensureTotalCapacityPrecise(gpa, 2); | ||
| 324 | q.pushFrontAssumeCapacity(0); | ||
| 325 | try q.ensureTotalCapacityPrecise(gpa, 3); | ||
| 326 | q.pushBackAssumeCapacity(2); | ||
| 327 | try q.ensureTotalCapacityPrecise(gpa, 5); | ||
| 328 | q.pushBackAssumeCapacity(3); | ||
| 329 | q.pushFrontAssumeCapacity(-1); | ||
| 330 | try q.ensureTotalCapacityPrecise(gpa, 6); | ||
| 331 | q.pushFrontAssumeCapacity(-2); | ||
| 332 | |||
| 333 | try testing.expectEqual(-2, q.popFront()); | ||
| 334 | try testing.expectEqual(-1, q.popFront()); | ||
| 335 | try testing.expectEqual(3, q.popBack()); | ||
| 336 | try testing.expectEqual(0, q.popFront()); | ||
| 337 | try testing.expectEqual(2, q.popBack()); | ||
| 338 | try testing.expectEqual(1, q.popBack()); | ||
| 339 | try testing.expectEqual(null, q.popFront()); | ||
| 340 | try testing.expectEqual(null, q.popBack()); | ||
| 341 | } | ||
| 342 | |||
| 343 | test "fuzz against ArrayList oracle" { | ||
| 344 | try std.testing.fuzz({}, fuzzAgainstArrayList, .{}); | ||
| 345 | } | ||
| 346 | |||
| 347 | test "dumb fuzz against ArrayList oracle" { | ||
| 348 | const testing = std.testing; | ||
| 349 | const gpa = testing.allocator; | ||
| 350 | |||
| 351 | const input = try gpa.alloc(u8, 1024); | ||
| 352 | defer gpa.free(input); | ||
| 353 | |||
| 354 | var prng = std.Random.DefaultPrng.init(testing.random_seed); | ||
| 355 | prng.random().bytes(input); | ||
| 356 | |||
| 357 | try fuzzAgainstArrayList({}, input); | ||
| 358 | } | ||
| 359 | |||
| 360 | fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void { | ||
| 361 | const testing = std.testing; | ||
| 362 | const gpa = testing.allocator; | ||
| 363 | |||
| 364 | var q: Deque(u32) = .empty; | ||
| 365 | defer q.deinit(gpa); | ||
| 366 | var l: std.ArrayList(u32) = .empty; | ||
| 367 | defer l.deinit(gpa); | ||
| 368 | |||
| 369 | if (input.len < 2) return; | ||
| 370 | |||
| 371 | var prng = std.Random.DefaultPrng.init(input[0]); | ||
| 372 | const random = prng.random(); | ||
| 373 | |||
| 374 | const Action = enum { | ||
| 375 | push_back, | ||
| 376 | push_front, | ||
| 377 | pop_back, | ||
| 378 | pop_front, | ||
| 379 | grow, | ||
| 380 | /// Sentinel to avoid hardcoding the cast below | ||
| 381 | max, | ||
| 382 | }; | ||
| 383 | for (input[1..]) |byte| { | ||
| 384 | switch (@as(Action, @enumFromInt(byte % (@intFromEnum(Action.max))))) { | ||
| 385 | .push_back => { | ||
| 386 | const item = random.int(u8); | ||
| 387 | try testing.expectEqual( | ||
| 388 | l.appendBounded(item), | ||
| 389 | q.pushBackBounded(item), | ||
| 390 | ); | ||
| 391 | }, | ||
| 392 | .push_front => { | ||
| 393 | const item = random.int(u8); | ||
| 394 | try testing.expectEqual( | ||
| 395 | l.insertBounded(0, item), | ||
| 396 | q.pushFrontBounded(item), | ||
| 397 | ); | ||
| 398 | }, | ||
| 399 | .pop_back => { | ||
| 400 | try testing.expectEqual(l.pop(), q.popBack()); | ||
| 401 | }, | ||
| 402 | .pop_front => { | ||
| 403 | try testing.expectEqual( | ||
| 404 | if (l.items.len > 0) l.orderedRemove(0) else null, | ||
| 405 | q.popFront(), | ||
| 406 | ); | ||
| 407 | }, | ||
| 408 | // Growing by small, random, linear amounts seems to better test | ||
| 409 | // ensureTotalCapacityPrecise(), which is the most complex part | ||
| 410 | // of the Deque implementation. | ||
| 411 | .grow => { | ||
| 412 | const growth = random.int(u3); | ||
| 413 | try l.ensureTotalCapacityPrecise(gpa, l.items.len + growth); | ||
| 414 | try q.ensureTotalCapacityPrecise(gpa, q.len + growth); | ||
| 415 | }, | ||
| 416 | .max => unreachable, | ||
| 417 | } | ||
| 418 | try testing.expectEqual(l.getLastOrNull(), q.back()); | ||
| 419 | try testing.expectEqual( | ||
| 420 | if (l.items.len > 0) l.items[0] else null, | ||
| 421 | q.front(), | ||
| 422 | ); | ||
| 423 | try testing.expectEqual(l.items.len, q.len); | ||
| 424 | try testing.expectEqual(l.capacity, q.buffer.len); | ||
| 425 | { | ||
| 426 | var it = q.iterator(); | ||
| 427 | for (l.items) |item| { | ||
| 428 | try testing.expectEqual(item, it.next()); | ||
| 429 | } | ||
| 430 | try testing.expectEqual(null, it.next()); | ||
| 431 | } | ||
| 432 | } | ||
| 433 | } | ||
lib/std/std.zig+1| ... | @@ -10,6 +10,7 @@ pub const BufMap = @import("buf_map.zig").BufMap; | ... | @@ -10,6 +10,7 @@ pub const BufMap = @import("buf_map.zig").BufMap; |
| 10 | pub const BufSet = @import("buf_set.zig").BufSet; | 10 | pub const BufSet = @import("buf_set.zig").BufSet; |
| 11 | pub const StaticStringMap = static_string_map.StaticStringMap; | 11 | pub const StaticStringMap = static_string_map.StaticStringMap; |
| 12 | pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql; | 12 | pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql; |
| 13 | pub const Deque = @import("deque.zig").Deque; | ||
| 13 | pub const DoublyLinkedList = @import("DoublyLinkedList.zig"); | 14 | pub const DoublyLinkedList = @import("DoublyLinkedList.zig"); |
| 14 | pub const DynLib = @import("dynamic_library.zig").DynLib; | 15 | pub const DynLib = @import("dynamic_library.zig").DynLib; |
| 15 | pub const DynamicBitSet = bit_set.DynamicBitSet; | 16 | pub const DynamicBitSet = bit_set.DynamicBitSet; |
src/Compilation.zig+21-22| ... | @@ -45,8 +45,6 @@ const Builtin = @import("Builtin.zig"); | ... | @@ -45,8 +45,6 @@ const Builtin = @import("Builtin.zig"); |
| 45 | const LlvmObject = @import("codegen/llvm.zig").Object; | 45 | const LlvmObject = @import("codegen/llvm.zig").Object; |
| 46 | const dev = @import("dev.zig"); | 46 | const dev = @import("dev.zig"); |
| 47 | 47 | ||
| 48 | const DeprecatedLinearFifo = @import("deprecated.zig").LinearFifo; | ||
| 49 | |||
| 50 | pub const Config = @import("Compilation/Config.zig"); | 48 | pub const Config = @import("Compilation/Config.zig"); |
| 51 | 49 | ||
| 52 | /// General-purpose allocator. Used for both temporary and long-term storage. | 50 | /// General-purpose allocator. Used for both temporary and long-term storage. |
| ... | @@ -124,20 +122,21 @@ work_queues: [ | ... | @@ -124,20 +122,21 @@ work_queues: [ |
| 124 | } | 122 | } |
| 125 | break :len len; | 123 | break :len len; |
| 126 | } | 124 | } |
| 127 | ]DeprecatedLinearFifo(Job), | 125 | ]std.Deque(Job), |
| 128 | 126 | ||
| 129 | /// These jobs are to invoke the Clang compiler to create an object file, which | 127 | /// These jobs are to invoke the Clang compiler to create an object file, which |
| 130 | /// gets linked with the Compilation. | 128 | /// gets linked with the Compilation. |
| 131 | c_object_work_queue: DeprecatedLinearFifo(*CObject), | 129 | c_object_work_queue: std.Deque(*CObject), |
| 132 | 130 | ||
| 133 | /// These jobs are to invoke the RC compiler to create a compiled resource file (.res), which | 131 | /// These jobs are to invoke the RC compiler to create a compiled resource file (.res), which |
| 134 | /// gets linked with the Compilation. | 132 | /// gets linked with the Compilation. |
| 135 | win32_resource_work_queue: if (dev.env.supports(.win32_resource)) DeprecatedLinearFifo(*Win32Resource) else struct { | 133 | win32_resource_work_queue: if (dev.env.supports(.win32_resource)) std.Deque(*Win32Resource) else struct { |
| 136 | pub fn ensureUnusedCapacity(_: @This(), _: u0) error{}!void {} | 134 | pub const empty: @This() = .{}; |
| 137 | pub fn readItem(_: @This()) ?noreturn { | 135 | pub fn ensureUnusedCapacity(_: @This(), _: Allocator, _: u0) error{}!void {} |
| 136 | pub fn popFront(_: @This()) ?noreturn { | ||
| 138 | return null; | 137 | return null; |
| 139 | } | 138 | } |
| 140 | pub fn deinit(_: @This()) void {} | 139 | pub fn deinit(_: @This(), _: Allocator) void {} |
| 141 | }, | 140 | }, |
| 142 | 141 | ||
| 143 | /// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator. | 142 | /// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator. |
| ... | @@ -2236,9 +2235,9 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options | ... | @@ -2236,9 +2235,9 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options |
| 2236 | .root_mod = options.root_mod, | 2235 | .root_mod = options.root_mod, |
| 2237 | .config = options.config, | 2236 | .config = options.config, |
| 2238 | .dirs = options.dirs, | 2237 | .dirs = options.dirs, |
| 2239 | .work_queues = @splat(.init(gpa)), | 2238 | .work_queues = @splat(.empty), |
| 2240 | .c_object_work_queue = .init(gpa), | 2239 | .c_object_work_queue = .empty, |
| 2241 | .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) .init(gpa) else .{}, | 2240 | .win32_resource_work_queue = .empty, |
| 2242 | .c_source_files = options.c_source_files, | 2241 | .c_source_files = options.c_source_files, |
| 2243 | .rc_source_files = options.rc_source_files, | 2242 | .rc_source_files = options.rc_source_files, |
| 2244 | .cache_parent = cache, | 2243 | .cache_parent = cache, |
| ... | @@ -2702,9 +2701,9 @@ pub fn destroy(comp: *Compilation) void { | ... | @@ -2702,9 +2701,9 @@ pub fn destroy(comp: *Compilation) void { |
| 2702 | if (comp.zcu) |zcu| zcu.deinit(); | 2701 | if (comp.zcu) |zcu| zcu.deinit(); |
| 2703 | comp.cache_use.deinit(); | 2702 | comp.cache_use.deinit(); |
| 2704 | 2703 | ||
| 2705 | for (&comp.work_queues) |*work_queue| work_queue.deinit(); | 2704 | for (&comp.work_queues) |*work_queue| work_queue.deinit(gpa); |
| 2706 | comp.c_object_work_queue.deinit(); | 2705 | comp.c_object_work_queue.deinit(gpa); |
| 2707 | comp.win32_resource_work_queue.deinit(); | 2706 | comp.win32_resource_work_queue.deinit(gpa); |
| 2708 | 2707 | ||
| 2709 | for (comp.windows_libs.keys()) |windows_lib| gpa.free(windows_lib); | 2708 | for (comp.windows_libs.keys()) |windows_lib| gpa.free(windows_lib); |
| 2710 | comp.windows_libs.deinit(gpa); | 2709 | comp.windows_libs.deinit(gpa); |
| ... | @@ -3019,17 +3018,17 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE | ... | @@ -3019,17 +3018,17 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 3019 | 3018 | ||
| 3020 | // For compiling C objects, we rely on the cache hash system to avoid duplicating work. | 3019 | // For compiling C objects, we rely on the cache hash system to avoid duplicating work. |
| 3021 | // Add a Job for each C object. | 3020 | // Add a Job for each C object. |
| 3022 | try comp.c_object_work_queue.ensureUnusedCapacity(comp.c_object_table.count()); | 3021 | try comp.c_object_work_queue.ensureUnusedCapacity(gpa, comp.c_object_table.count()); |
| 3023 | for (comp.c_object_table.keys()) |c_object| { | 3022 | for (comp.c_object_table.keys()) |c_object| { |
| 3024 | comp.c_object_work_queue.writeItemAssumeCapacity(c_object); | 3023 | comp.c_object_work_queue.pushBackAssumeCapacity(c_object); |
| 3025 | try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{c_object.src.src_path})); | 3024 | try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{c_object.src.src_path})); |
| 3026 | } | 3025 | } |
| 3027 | 3026 | ||
| 3028 | // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work. | 3027 | // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work. |
| 3029 | // Add a Job for each Win32 resource file. | 3028 | // Add a Job for each Win32 resource file. |
| 3030 | try comp.win32_resource_work_queue.ensureUnusedCapacity(comp.win32_resource_table.count()); | 3029 | try comp.win32_resource_work_queue.ensureUnusedCapacity(gpa, comp.win32_resource_table.count()); |
| 3031 | for (comp.win32_resource_table.keys()) |win32_resource| { | 3030 | for (comp.win32_resource_table.keys()) |win32_resource| { |
| 3032 | comp.win32_resource_work_queue.writeItemAssumeCapacity(win32_resource); | 3031 | comp.win32_resource_work_queue.pushBackAssumeCapacity(win32_resource); |
| 3033 | switch (win32_resource.src) { | 3032 | switch (win32_resource.src) { |
| 3034 | .rc => |f| { | 3033 | .rc => |f| { |
| 3035 | try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{f.src_path})); | 3034 | try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{f.src_path})); |
| ... | @@ -4871,14 +4870,14 @@ fn performAllTheWork( | ... | @@ -4871,14 +4870,14 @@ fn performAllTheWork( |
| 4871 | } | 4870 | } |
| 4872 | } | 4871 | } |
| 4873 | 4872 | ||
| 4874 | while (comp.c_object_work_queue.readItem()) |c_object| { | 4873 | while (comp.c_object_work_queue.popFront()) |c_object| { |
| 4875 | comp.link_task_queue.startPrelinkItem(); | 4874 | comp.link_task_queue.startPrelinkItem(); |
| 4876 | comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateCObject, .{ | 4875 | comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateCObject, .{ |
| 4877 | comp, c_object, main_progress_node, | 4876 | comp, c_object, main_progress_node, |
| 4878 | }); | 4877 | }); |
| 4879 | } | 4878 | } |
| 4880 | 4879 | ||
| 4881 | while (comp.win32_resource_work_queue.readItem()) |win32_resource| { | 4880 | while (comp.win32_resource_work_queue.popFront()) |win32_resource| { |
| 4882 | comp.link_task_queue.startPrelinkItem(); | 4881 | comp.link_task_queue.startPrelinkItem(); |
| 4883 | comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateWin32Resource, .{ | 4882 | comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateWin32Resource, .{ |
| 4884 | comp, win32_resource, main_progress_node, | 4883 | comp, win32_resource, main_progress_node, |
| ... | @@ -4998,7 +4997,7 @@ fn performAllTheWork( | ... | @@ -4998,7 +4997,7 @@ fn performAllTheWork( |
| 4998 | } | 4997 | } |
| 4999 | 4998 | ||
| 5000 | work: while (true) { | 4999 | work: while (true) { |
| 5001 | for (&comp.work_queues) |*work_queue| if (work_queue.readItem()) |job| { | 5000 | for (&comp.work_queues) |*work_queue| if (work_queue.popFront()) |job| { |
| 5002 | try processOneJob(@intFromEnum(Zcu.PerThread.Id.main), comp, job); | 5001 | try processOneJob(@intFromEnum(Zcu.PerThread.Id.main), comp, job); |
| 5003 | continue :work; | 5002 | continue :work; |
| 5004 | }; | 5003 | }; |
| ... | @@ -5027,7 +5026,7 @@ fn performAllTheWork( | ... | @@ -5027,7 +5026,7 @@ fn performAllTheWork( |
| 5027 | const JobError = Allocator.Error; | 5026 | const JobError = Allocator.Error; |
| 5028 | 5027 | ||
| 5029 | pub fn queueJob(comp: *Compilation, job: Job) !void { | 5028 | pub fn queueJob(comp: *Compilation, job: Job) !void { |
| 5030 | try comp.work_queues[Job.stage(job)].writeItem(job); | 5029 | try comp.work_queues[Job.stage(job)].pushBack(comp.gpa, job); |
| 5031 | } | 5030 | } |
| 5032 | 5031 | ||
| 5033 | pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void { | 5032 | pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void { |
src/deprecated.zig deleted-169| ... | @@ -1,169 +0,0 @@ | ||
| 1 | //! Deprecated. Stop using this API | ||
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const math = std.math; | ||
| 5 | const mem = std.mem; | ||
| 6 | const Allocator = mem.Allocator; | ||
| 7 | const assert = std.debug.assert; | ||
| 8 | const testing = std.testing; | ||
| 9 | |||
| 10 | pub fn LinearFifo(comptime T: type) type { | ||
| 11 | return struct { | ||
| 12 | allocator: Allocator, | ||
| 13 | buf: []T, | ||
| 14 | head: usize, | ||
| 15 | count: usize, | ||
| 16 | |||
| 17 | const Self = @This(); | ||
| 18 | |||
| 19 | pub fn init(allocator: Allocator) Self { | ||
| 20 | return .{ | ||
| 21 | .allocator = allocator, | ||
| 22 | .buf = &.{}, | ||
| 23 | .head = 0, | ||
| 24 | .count = 0, | ||
| 25 | }; | ||
| 26 | } | ||
| 27 | |||
| 28 | pub fn deinit(self: *Self) void { | ||
| 29 | self.allocator.free(self.buf); | ||
| 30 | self.* = undefined; | ||
| 31 | } | ||
| 32 | |||
| 33 | pub fn realign(self: *Self) void { | ||
| 34 | if (self.buf.len - self.head >= self.count) { | ||
| 35 | mem.copyForwards(T, self.buf[0..self.count], self.buf[self.head..][0..self.count]); | ||
| 36 | self.head = 0; | ||
| 37 | } else { | ||
| 38 | var tmp: [4096 / 2 / @sizeOf(T)]T = undefined; | ||
| 39 | |||
| 40 | while (self.head != 0) { | ||
| 41 | const n = @min(self.head, tmp.len); | ||
| 42 | const m = self.buf.len - n; | ||
| 43 | @memcpy(tmp[0..n], self.buf[0..n]); | ||
| 44 | mem.copyForwards(T, self.buf[0..m], self.buf[n..][0..m]); | ||
| 45 | @memcpy(self.buf[m..][0..n], tmp[0..n]); | ||
| 46 | self.head -= n; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | { // set unused area to undefined | ||
| 50 | const unused = mem.sliceAsBytes(self.buf[self.count..]); | ||
| 51 | @memset(unused, undefined); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | /// Ensure that the buffer can fit at least `size` items | ||
| 56 | pub fn ensureTotalCapacity(self: *Self, size: usize) !void { | ||
| 57 | if (self.buf.len >= size) return; | ||
| 58 | self.realign(); | ||
| 59 | const new_size = math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory; | ||
| 60 | self.buf = try self.allocator.realloc(self.buf, new_size); | ||
| 61 | } | ||
| 62 | |||
| 63 | /// Makes sure at least `size` items are unused | ||
| 64 | pub fn ensureUnusedCapacity(self: *Self, size: usize) error{OutOfMemory}!void { | ||
| 65 | if (self.writableLength() >= size) return; | ||
| 66 | |||
| 67 | return try self.ensureTotalCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory); | ||
| 68 | } | ||
| 69 | |||
| 70 | /// Returns a writable slice from the 'read' end of the fifo | ||
| 71 | fn readableSliceMut(self: Self, offset: usize) []T { | ||
| 72 | if (offset > self.count) return &[_]T{}; | ||
| 73 | |||
| 74 | var start = self.head + offset; | ||
| 75 | if (start >= self.buf.len) { | ||
| 76 | start -= self.buf.len; | ||
| 77 | return self.buf[start .. start + (self.count - offset)]; | ||
| 78 | } else { | ||
| 79 | const end = @min(self.head + self.count, self.buf.len); | ||
| 80 | return self.buf[start..end]; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | /// Discard first `count` items in the fifo | ||
| 85 | pub fn discard(self: *Self, count: usize) void { | ||
| 86 | assert(count <= self.count); | ||
| 87 | { // set old range to undefined. Note: may be wrapped around | ||
| 88 | const slice = self.readableSliceMut(0); | ||
| 89 | if (slice.len >= count) { | ||
| 90 | const unused = mem.sliceAsBytes(slice[0..count]); | ||
| 91 | @memset(unused, undefined); | ||
| 92 | } else { | ||
| 93 | const unused = mem.sliceAsBytes(slice[0..]); | ||
| 94 | @memset(unused, undefined); | ||
| 95 | const unused2 = mem.sliceAsBytes(self.readableSliceMut(slice.len)[0 .. count - slice.len]); | ||
| 96 | @memset(unused2, undefined); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | var head = self.head + count; | ||
| 100 | // Note it is safe to do a wrapping subtract as | ||
| 101 | // bitwise & with all 1s is a noop | ||
| 102 | head &= self.buf.len -% 1; | ||
| 103 | self.head = head; | ||
| 104 | self.count -= count; | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Read the next item from the fifo | ||
| 108 | pub fn readItem(self: *Self) ?T { | ||
| 109 | if (self.count == 0) return null; | ||
| 110 | |||
| 111 | const c = self.buf[self.head]; | ||
| 112 | self.discard(1); | ||
| 113 | return c; | ||
| 114 | } | ||
| 115 | |||
| 116 | /// Returns number of items available in fifo | ||
| 117 | pub fn writableLength(self: Self) usize { | ||
| 118 | return self.buf.len - self.count; | ||
| 119 | } | ||
| 120 | |||
| 121 | /// Returns the first section of writable buffer. | ||
| 122 | /// Note that this may be of length 0 | ||
| 123 | pub fn writableSlice(self: Self, offset: usize) []T { | ||
| 124 | if (offset > self.buf.len) return &[_]T{}; | ||
| 125 | |||
| 126 | const tail = self.head + offset + self.count; | ||
| 127 | if (tail < self.buf.len) { | ||
| 128 | return self.buf[tail..]; | ||
| 129 | } else { | ||
| 130 | return self.buf[tail - self.buf.len ..][0 .. self.writableLength() - offset]; | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | /// Update the tail location of the buffer (usually follows use of writable/writableWithSize) | ||
| 135 | pub fn update(self: *Self, count: usize) void { | ||
| 136 | assert(self.count + count <= self.buf.len); | ||
| 137 | self.count += count; | ||
| 138 | } | ||
| 139 | |||
| 140 | /// Appends the data in `src` to the fifo. | ||
| 141 | /// You must have ensured there is enough space. | ||
| 142 | pub fn writeAssumeCapacity(self: *Self, src: []const T) void { | ||
| 143 | assert(self.writableLength() >= src.len); | ||
| 144 | |||
| 145 | var src_left = src; | ||
| 146 | while (src_left.len > 0) { | ||
| 147 | const writable_slice = self.writableSlice(0); | ||
| 148 | assert(writable_slice.len != 0); | ||
| 149 | const n = @min(writable_slice.len, src_left.len); | ||
| 150 | @memcpy(writable_slice[0..n], src_left[0..n]); | ||
| 151 | self.update(n); | ||
| 152 | src_left = src_left[n..]; | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | /// Write a single item to the fifo | ||
| 157 | pub fn writeItem(self: *Self, item: T) !void { | ||
| 158 | try self.ensureUnusedCapacity(1); | ||
| 159 | return self.writeItemAssumeCapacity(item); | ||
| 160 | } | ||
| 161 | |||
| 162 | pub fn writeItemAssumeCapacity(self: *Self, item: T) void { | ||
| 163 | var tail = self.head + self.count; | ||
| 164 | tail &= self.buf.len - 1; | ||
| 165 | self.buf[tail] = item; | ||
| 166 | self.update(1); | ||
| 167 | } | ||
| 168 | }; | ||
| 169 | } | ||