authorgravatar for tgschultz@gmail.comtgschultz <tgschultz@gmail.com> 2019-05-04 00:28:51+00:00
committergravatar for tgschultz@gmail.comtgschultz <tgschultz@gmail.com> 2019-05-04 00:28:51+00:00
loge2fd37e75b0b309ee8e2bf6c3678da87d84b369d
treec06721331bcb9c699c39537459f74a0e1f31bf1d
parentf4798297ded2d05f4e1919fb27a9cdb9981a5e11

Added PackedIntArray, PackedIntSlice to std


1 files changed, 599 insertions(+), 0 deletions(-)

std/packed_int_array.zig created+599
...@@ -0,0 +1,599 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const debug = std.debug;
4const testing = std.testing;
5
6pub fn PackedIntIo(comptime Int: type) type
7{
8 //The general technique employed here is to cast bytes in the array to a container
9 // integer (having bits % 8 == 0) large enough to contain the number of bits we want,
10 // then we can retrieve or store the new value with a relative minimum of masking
11 // and shifting. In this worst case, this means that we'll need an integer that's
12 // actually 1 byte larger than the minimum required to store the bits, because it
13 // is possible that the bits start at the end of the first byte, continue through
14 // zero or more, then end in the beginning of the last. But, if we try to access
15 // a value in the very last byte of memory with that integer size, that extra byte
16 // will be out of bounds. Depending on the circumstances of the memory, that might
17 // mean the OS fatally kills the program. Thus, we use a larger container (MaxIo)
18 // most of the time, but a smaller container (MinIo) when touching the last byte
19 // of the memory.
20
21 const int_bits = comptime std.meta.bitCount(Int);
22
23 //in the best case, this is the number of bytes we need to touch
24 // to read or write a value, as bits
25 const min_io_bits = ((int_bits + 7) / 8) * 8;
26
27 //in the worst case, this is the number of bytes we need to touch
28 // to read or write a value, as bits
29 const max_io_bits = switch(int_bits)
30 {
31 0 => 0,
32 1 => 8,
33 2...9 => 16,
34 10...65535 => ((int_bits / 8) + 2) * 8,
35 else => unreachable,
36 };
37
38 //we bitcast the desired Int type to an unsigned version of itself
39 // to avoid issues with shifting signed ints.
40 const UnInt = @IntType(false, int_bits);
41
42 //The maximum container int type
43 const MinIo = @IntType(false, min_io_bits);
44
45 //The minimum container int type
46 const MaxIo = @IntType(false, max_io_bits);
47
48 return struct
49 {
50 pub fn get(bytes: []const u8, index: usize, bit_offset: u7) Int
51 {
52 if(int_bits == 0) return 0;
53
54 const bit_index = (index * int_bits) + bit_offset;
55 const max_end_byte = (bit_index + max_io_bits) / 8;
56
57 //Using the larger container size will potentially read out of bounds
58 if(max_end_byte > bytes.len) return getBits(bytes, MinIo, bit_index);
59 return getBits(bytes, MaxIo, bit_index);
60 }
61
62 fn getBits(bytes: []const u8, comptime Container: type, bit_index: usize) Int
63 {
64 const container_bits = comptime std.meta.bitCount(Container);
65 const Shift = std.math.Log2Int(Container);
66
67 const start_byte = bit_index / 8;
68 const head_keep_bits = bit_index - (start_byte * 8);
69 const tail_keep_bits = container_bits - (int_bits + head_keep_bits);
70
71 //read bytes as container
72 const value_ptr = @ptrCast(*const align(1) Container, &bytes[start_byte]);
73 var value = value_ptr.*;
74
75 switch(builtin.endian)
76 {
77 .Big =>
78 {
79 value <<= @intCast(Shift, head_keep_bits);
80 value >>= @intCast(Shift, head_keep_bits);
81 value >>= @intCast(Shift, tail_keep_bits);
82 },
83 .Little =>
84 {
85 value <<= @intCast(Shift, tail_keep_bits);
86 value >>= @intCast(Shift, tail_keep_bits);
87 value >>= @intCast(Shift, head_keep_bits);
88 },
89 }
90
91 return @bitCast(Int, @truncate(UnInt, value));
92 }
93
94 pub fn set(bytes: []u8, index: usize, bit_offset: u3, int: Int) void
95 {
96 if(int_bits == 0) return;
97
98 const bit_index = (index * int_bits) + bit_offset;
99 const max_end_byte = (bit_index + max_io_bits) / 8;
100
101 //Using the larger container size will potentially write out of bounds
102 if(max_end_byte > bytes.len) return setBits(bytes, MinIo, bit_index, int);
103 setBits(bytes, MaxIo, bit_index, int);
104 }
105
106 fn setBits(bytes: []u8, comptime Container: type, bit_index: usize, int: Int) void
107 {
108 const container_bits = comptime std.meta.bitCount(Container);
109 const Shift = std.math.Log2Int(Container);
110
111 const start_byte = bit_index / 8;
112 const head_keep_bits = bit_index - (start_byte * 8);
113 const tail_keep_bits = container_bits - (int_bits + head_keep_bits);
114 const keep_shift = switch(builtin.endian)
115 {
116 .Big => @intCast(Shift, tail_keep_bits),
117 .Little => @intCast(Shift, head_keep_bits),
118 };
119
120 //position the bits where they need to be in the container
121 const value = @intCast(Container, @bitCast(UnInt, int)) << keep_shift;
122
123 //read existing bytes
124 const target_ptr = @ptrCast(*align(1) Container, &bytes[start_byte]);
125 var target = target_ptr.*;
126
127 //zero the bits we want to replace in the existing bytes
128 const inv_mask = @intCast(Container, std.math.maxInt(UnInt)) << keep_shift;
129 const mask = ~inv_mask;
130 target &= mask;
131
132 //merge the new value
133 target |= value;
134
135 //save it back
136 target_ptr.* = target;
137 }
138
139 fn slice(bytes: []u8, bit_offset: u3, start: usize, end: usize) PackedIntSlice(Int)
140 {
141 debug.assert(end >= start);
142
143 const length = end - start;
144 const bit_index = (start * int_bits) + bit_offset;
145 const start_byte = bit_index / 8;
146 const end_byte = (bit_index + (length * int_bits) + 7) / 8;
147 const new_bytes = bytes[start_byte..end_byte];
148
149 if(length == 0) return PackedIntSlice(Int).init(new_bytes[0..0], 0);
150
151 var new_slice = PackedIntSlice(Int).init(new_bytes, length);
152 new_slice.bit_offset = @intCast(u3, (bit_index - (start_byte * 8)));
153 return new_slice;
154 }
155
156 fn sliceCast(bytes: []u8, comptime NewInt: type, bit_offset: u3, old_len: usize)
157 PackedIntSlice(NewInt)
158 {
159 const new_int_bits = comptime std.meta.bitCount(NewInt);
160 const New = PackedIntSlice(NewInt);
161
162 const total_bits = (old_len * int_bits);
163 const new_int_count = total_bits / new_int_bits;
164
165 debug.assert(total_bits == new_int_count * new_int_bits);
166
167 var new = New.init(bytes, new_int_count);
168 new.bit_offset = bit_offset;
169 return new;
170 }
171 };
172}
173
174///Creates a bit-packed array of int_count integers of type Int. Bits
175/// are packed using native endianess and without storing any meta
176/// data. PackedArray(i3, 8) will occupy exactly 3 bytes of memory.
177pub fn PackedIntArray(comptime Int: type, comptime int_count: usize) type
178{
179 const int_bits = comptime std.meta.bitCount(Int);
180 const total_bits = int_bits * int_count;
181 const total_bytes = (total_bits + 7) / 8;
182
183 const Io = PackedIntIo(Int);
184
185 return struct
186 {
187 const Self = @This();
188
189 bytes: [total_bytes]u8,
190
191 ///Returns the number of elements in the packed array
192 pub fn len(self: Self) usize
193 {
194 return int_count;
195 }
196
197 ///Initialize a packed array using an unpacked array
198 /// or, more likely, an array literal.
199 pub fn init(ints: [int_count]Int) Self
200 {
201 var self = Self(undefined);
202 for(ints) |int, i| self.set(i, int);
203 return self;
204 }
205
206 ///Return the Int stored at index
207 pub fn get(self: Self, index: usize) Int
208 {
209 debug.assert(index < int_count);
210 return Io.get(self.bytes, index, 0);
211 }
212
213 ///Copy int into the array at index
214 pub fn set(self: *Self, index: usize, int: Int) void
215 {
216 debug.assert(index < int_count);
217 return Io.set(&self.bytes, index, 0, int);
218 }
219
220 ///Create a PackedIntSlice of the array from given start to given end
221 pub fn slice(self: *Self, start: usize, end: usize) PackedIntSlice(Int)
222 {
223 debug.assert(start < int_count);
224 debug.assert(end <= int_count);
225 return Io.slice(&self.bytes, 0, start, end);
226 }
227
228 ///Create a PackedIntSlice of the array using NewInt as the bit width integer.
229 /// NewInt's bit width must fit evenly within the array's Int's total bits.
230 pub fn sliceCast(self: *Self, comptime NewInt: type) PackedIntSlice(NewInt)
231 {
232 return Io.sliceCast(&self.bytes, NewInt, 0, int_count);
233 }
234 };
235}
236
237//@TODO: Add Slice Casting
238pub fn PackedIntSlice(comptime Int: type) type
239{
240 const int_bits = comptime std.meta.bitCount(Int);
241 const Io = PackedIntIo(Int);
242
243 return struct
244 {
245 const Self = @This();
246
247 bytes: []u8,
248 int_count: usize,
249 bit_offset: u3,
250
251 ///Returns the number of elements in the packed slice
252 pub fn len(self: Self) usize
253 {
254 return self.int_count;
255 }
256
257 ///Calculates the number of bytes required to store a desired count
258 /// of Ints
259 pub fn bytesRequired(int_count: usize) usize
260 {
261 const total_bits = int_bits * int_count;
262 const total_bytes = (total_bits + 7) / 8;
263 return total_bytes;
264 }
265
266 ///Initialize a packed slice using the memory at bytes, with int_count
267 /// elements. bytes must be large enough to accomodate the requested
268 /// count.
269 pub fn init(bytes: []u8, int_count: usize) Self
270 {
271 debug.assert(bytes.len >= bytesRequired(int_count));
272
273 return Self
274 {
275 .bytes = bytes,
276 .int_count = int_count,
277 .bit_offset = 0,
278 };
279 }
280
281 ///Return the Int stored at index
282 pub fn get(self: Self, index: usize) Int
283 {
284 debug.assert(index < self.int_count);
285 return Io.get(self.bytes, index, self.bit_offset);
286 }
287
288 ///Copy int into the array at index
289 pub fn set(self: *Self, index: usize, int: Int) void
290 {
291 debug.assert(index < self.int_count);
292 return Io.set(self.bytes, index, self.bit_offset, int);
293 }
294
295 ///Create a PackedIntSlice of this slice from given start to given end
296 pub fn slice(self: Self, start: usize, end: usize) PackedIntSlice(Int)
297 {
298 debug.assert(start < self.int_count);
299 debug.assert(end <= self.int_count);
300 return Io.slice(self.bytes, self.bit_offset, start, end);
301 }
302
303 ///Create a PackedIntSlice of this slice using NewInt as the bit width integer.
304 /// NewInt's bit width must fit evenly within this slice's Int's total bits.
305 pub fn sliceCast(self: Self, comptime NewInt: type) PackedIntSlice(NewInt)
306 {
307 return Io.sliceCast(self.bytes, NewInt, self.bit_offset, self.int_count);
308 }
309 };
310}
311
312test "PackedIntArray"
313{
314 @setEvalBranchQuota(10000);
315 const max_bits = 256;
316 const int_count = 19;
317
318 comptime var bits = 0;
319 inline while(bits <= 256):(bits += 1)
320 {
321 //alternate unsigned and signed
322 const even = bits % 2 == 0;
323 const I = @IntType(even, bits);
324
325 const PackedArray = PackedIntArray(I, int_count);
326 const expected_bytes = ((bits * int_count) + 7) / 8;
327 testing.expect(@sizeOf(PackedArray) == expected_bytes);
328
329 var data = PackedArray(undefined);
330
331 //write values, counting up
332 var i = usize(0);
333 var count = I(0);
334 while(i < data.len()):(i += 1)
335 {
336 data.set(i, count);
337 if(bits > 0) count +%= 1;
338 }
339
340 //read and verify values
341 i = 0;
342 count = 0;
343 while(i < data.len()):(i += 1)
344 {
345 const val = data.get(i);
346 testing.expect(val == count);
347 if(bits > 0) count +%= 1;
348 }
349 }
350}
351
352test "PackedIntArray init"
353{
354 const PackedArray = PackedIntArray(u3, 8);
355 var packed_array = PackedArray.init([]u3{0,1,2,3,4,5,6,7});
356 var i = usize(0);
357 while(i < packed_array.len()):(i += 1) testing.expect(packed_array.get(i) == i);
358}
359
360test "PackedIntSlice"
361{
362 @setEvalBranchQuota(10000);
363 const max_bits = 256;
364 const int_count = 19;
365 const total_bits = max_bits * int_count;
366 const total_bytes = (total_bits + 7) / 8;
367
368 var buffer: [total_bytes]u8 = undefined;
369
370 comptime var bits = 0;
371 inline while(bits <= 256):(bits += 1)
372 {
373 //alternate unsigned and signed
374 const even = bits % 2 == 0;
375 const I = @IntType(even, bits);
376 const P = PackedIntSlice(I);
377
378 var data = P.init(&buffer, int_count);
379
380 //write values, counting up
381 var i = usize(0);
382 var count = I(0);
383 while(i < data.len()):(i += 1)
384 {
385 data.set(i, count);
386 if(bits > 0) count +%= 1;
387 }
388
389 //read and verify values
390 i = 0;
391 count = 0;
392 while(i < data.len()):(i += 1)
393 {
394 const val = data.get(i);
395 testing.expect(val == count);
396 if(bits > 0) count +%= 1;
397 }
398 }
399}
400
401test "PackedIntSlice of PackedInt(Array/Slice)"
402{
403 const max_bits = 16;
404 const int_count = 19;
405
406 comptime var bits = 0;
407 inline while(bits <= max_bits):(bits += 1)
408 {
409 const Int = @IntType(false, bits);
410
411 const PackedArray = PackedIntArray(Int, int_count);
412 var packed_array = PackedArray(undefined);
413
414 const limit = (1 << bits);
415
416 var i = usize(0);
417 while(i < packed_array.len()):(i += 1)
418 {
419 packed_array.set(i, @intCast(Int, i % limit));
420 }
421
422 //slice of array
423 var packed_slice = packed_array.slice(2, 5);
424 testing.expect(packed_slice.len() == 3);
425 const ps_bit_count = (bits * packed_slice.len()) + packed_slice.bit_offset;
426 const ps_expected_bytes = (ps_bit_count + 7) / 8;
427 testing.expect(packed_slice.bytes.len == ps_expected_bytes);
428 testing.expect(packed_slice.get(0) == 2 % limit);
429 testing.expect(packed_slice.get(1) == 3 % limit);
430 testing.expect(packed_slice.get(2) == 4 % limit);
431 packed_slice.set(1, 7 % limit);
432 testing.expect(packed_slice.get(1) == 7 % limit);
433
434 //write through slice
435 testing.expect(packed_array.get(3) == 7 % limit);
436
437 //slice of a slice
438 const packed_slice_two = packed_slice.slice(0, 3);
439 testing.expect(packed_slice_two.len() == 3);
440 const ps2_bit_count = (bits * packed_slice_two.len()) + packed_slice_two.bit_offset;
441 const ps2_expected_bytes = (ps2_bit_count + 7) / 8;
442 testing.expect(packed_slice_two.bytes.len == ps2_expected_bytes);
443 testing.expect(packed_slice_two.get(1) == 7 % limit);
444 testing.expect(packed_slice_two.get(2) == 4 % limit);
445
446 //size one case
447 const packed_slice_three = packed_slice_two.slice(1, 2);
448 testing.expect(packed_slice_three.len() == 1);
449 const ps3_bit_count = (bits * packed_slice_three.len()) + packed_slice_three.bit_offset;
450 const ps3_expected_bytes = (ps3_bit_count + 7) / 8;
451 testing.expect(packed_slice_three.bytes.len == ps3_expected_bytes);
452 testing.expect(packed_slice_three.get(0) == 7 % limit);
453
454 //empty slice case
455 const packed_slice_empty = packed_slice.slice(0, 0);
456 testing.expect(packed_slice_empty.len() == 0);
457 testing.expect(packed_slice_empty.bytes.len == 0);
458
459 //slicing at byte boundaries
460 const packed_slice_edge = packed_array.slice(8, 16);
461 testing.expect(packed_slice_edge.len() == 8);
462 const pse_bit_count = (bits * packed_slice_edge.len()) + packed_slice_edge.bit_offset;
463 const pse_expected_bytes = (pse_bit_count + 7) / 8;
464 testing.expect(packed_slice_edge.bytes.len == pse_expected_bytes);
465 testing.expect(packed_slice_edge.bit_offset == 0);
466 }
467
468}
469
470test "PackedIntSlice accumulating bit offsets"
471{
472 //bit_offset is u3, so standard debugging asserts should catch
473 // anything
474 {
475 const PackedArray = PackedIntArray(u3, 16);
476 var packed_array = PackedArray(undefined);
477
478 var packed_slice = packed_array.slice(0, packed_array.len());
479 var i = usize(0);
480 while(i < packed_array.len() - 1):(i += 1)
481 {
482
483 packed_slice = packed_slice.slice(1, packed_slice.len());
484 }
485 }
486 {
487 const PackedArray = PackedIntArray(u11, 88);
488 var packed_array = PackedArray(undefined);
489
490 var packed_slice = packed_array.slice(0, packed_array.len());
491 var i = usize(0);
492 while(i < packed_array.len() - 1):(i += 1)
493 {
494 packed_slice = packed_slice.slice(1, packed_slice.len());
495 }
496 }
497
498}
499
500//@NOTE: As I do not have a big endian system to test this on,
501// big endian values were not tested
502test "PackedInt(Array/Slice) sliceCast"
503{
504 const PackedArray = PackedIntArray(u1, 16);
505 var packed_array = PackedArray.init([]u1{0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1});
506 const packed_slice_cast_2 = packed_array.sliceCast(u2);
507 const packed_slice_cast_4 = packed_slice_cast_2.sliceCast(u4);
508 var packed_slice_cast_9 = packed_array.slice(0, (packed_array.len() / 9) * 9).sliceCast(u9);
509 const packed_slice_cast_3 = packed_slice_cast_9.sliceCast(u3);
510
511 var i = usize(0);
512 while(i < packed_slice_cast_2.len()):(i += 1)
513 {
514 const val = switch(builtin.endian)
515 {
516 .Big => 0b01,
517 .Little => 0b10,
518 };
519 testing.expect(packed_slice_cast_2.get(i) == val);
520 }
521 i = 0;
522 while(i < packed_slice_cast_4.len()):(i += 1)
523 {
524 const val = switch(builtin.endian)
525 {
526 .Big => 0b0101,
527 .Little => 0b1010,
528 };
529 testing.expect(packed_slice_cast_4.get(i) == val);
530 }
531 i = 0;
532 while(i < packed_slice_cast_9.len()):(i += 1)
533 {
534 const val = 0b010101010;
535 testing.expect(packed_slice_cast_9.get(i) == val);
536 packed_slice_cast_9.set(i, 0b111000111);
537 }
538 i = 0;
539 while(i < packed_slice_cast_3.len()):(i += 1)
540 {
541 const val = switch(builtin.endian)
542 {
543 .Big => if(i % 2 == 0) u3(0b111) else u3(0b000),
544 .Little => if(i % 2 == 0) u3(0b111) else u3(0b000),
545 };
546 testing.expect(packed_slice_cast_3.get(i) == val);
547 }
548}
549
550//@NOTE: Need to manually update this list as more posix os's get
551// added to DirectAllocator. Windows can be added too when DirectAllocator
552// switches to VirtualAlloc.
553
554//These tests prove we aren't accidentally accessing memory past
555// the end of the array/slice by placing it at the end of a page
556// and reading the last element. The assumption is that the page
557// after this one is not mapped and will cause a segfault if we
558// don't account for the bounds.
559test "PackedIntArray at end of available memory"
560{
561 switch(builtin.os)
562 {
563 .linux, .macosx, .ios, .freebsd, .netbsd => {},
564 else => return,
565 }
566 const PackedArray = PackedIntArray(u3, 8);
567
568 const Padded = struct
569 {
570 _: [std.os.page_size - @sizeOf(PackedArray)]u8,
571 p: PackedArray,
572 };
573
574 var da = std.heap.DirectAllocator.init();
575 const allocator = &da.allocator;
576
577 var pad = try allocator.create(Padded);
578 defer allocator.destroy(pad);
579 pad.p.set(7, std.math.maxInt(u3));
580}
581
582test "PackedIntSlice at end of available memory"
583{
584 switch(builtin.os)
585 {
586 .linux, .macosx, .ios, .freebsd, .netbsd => {},
587 else => return,
588 }
589 const PackedSlice = PackedIntSlice(u11);
590
591 var da = std.heap.DirectAllocator.init();
592 const allocator = &da.allocator;
593
594 var page = try allocator.alloc(u8, std.os.page_size);
595 defer allocator.free(page);
596
597 var p = PackedSlice.init(page[std.os.page_size - 2..], 1);
598 p.set(0, std.math.maxInt(u11));
599}