Merge remote-tracking branch 'origin/master' into rewrite-coroutines

This commit is contained in:
Andrew Kelley 2019-08-04 19:08:44 -04:00
commit b7c94be688
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
17 changed files with 745 additions and 150 deletions

View File

@ -25,6 +25,7 @@ Here are some examples:
* [Iterative Replacement of C with Zig](http://tiehuis.github.io/blog/zig1.html) * [Iterative Replacement of C with Zig](http://tiehuis.github.io/blog/zig1.html)
* [The Right Tool for the Right Job: Redis Modules & Zig](https://www.youtube.com/watch?v=eCHM8-_poZY) * [The Right Tool for the Right Job: Redis Modules & Zig](https://www.youtube.com/watch?v=eCHM8-_poZY)
* [Writing a small ray tracer in Rust and Zig](https://nelari.us/post/raytracer_with_rust_and_zig/)
Zig is a brand new language, with no advertising budget. Word of mouth is the Zig is a brand new language, with no advertising budget. Word of mouth is the
only way people find out about the project, and the more people hear about it, only way people find out about the project, and the more people hear about it,
@ -45,8 +46,8 @@ The most highly regarded argument in such a discussion is a real world use case.
The issue label The issue label
[Contributor Friendly](https://github.com/ziglang/zig/issues?q=is%3Aissue+is%3Aopen+label%3A%22contributor+friendly%22) [Contributor Friendly](https://github.com/ziglang/zig/issues?q=is%3Aissue+is%3Aopen+label%3A%22contributor+friendly%22)
exists to help contributors find issues that are "limited in scope and/or exists to help you find issues that are **limited in scope and/or
knowledge of Zig internals." knowledge of Zig internals.**
### Editing Source Code ### Editing Source Code
@ -61,8 +62,7 @@ To test changes, do the following from the build directory:
1. Run `make install` (on POSIX) or 1. Run `make install` (on POSIX) or
`msbuild -p:Configuration=Release INSTALL.vcxproj` (on Windows). `msbuild -p:Configuration=Release INSTALL.vcxproj` (on Windows).
2. `bin/zig build --build-file ../build.zig test` (on POSIX) or 2. `bin/zig build test` (on POSIX) or `bin\zig.exe build test` (on Windows).
`bin\zig.exe build --build-file ..\build.zig test` (on Windows).
That runs the whole test suite, which does a lot of extra testing that you That runs the whole test suite, which does a lot of extra testing that you
likely won't always need, and can take upwards of 2 hours. This is what the likely won't always need, and can take upwards of 2 hours. This is what the
@ -79,8 +79,8 @@ Another example is choosing a different set of things to test. For example,
not the other ones. Combining this suggestion with the previous one, you could not the other ones. Combining this suggestion with the previous one, you could
do this: do this:
`bin/zig build --build-file ../build.zig test-std -Dskip-release` (on POSIX) or `bin/zig build test-std -Dskip-release` (on POSIX) or
`bin\zig.exe build --build-file ..\build.zig test-std -Dskip-release` (on Windows). `bin\zig.exe build test-std -Dskip-release` (on Windows).
This will run only the standard library tests, in debug mode only, for all This will run only the standard library tests, in debug mode only, for all
targets (it will cross-compile the tests for non-native targets but not run targets (it will cross-compile the tests for non-native targets but not run

View File

@ -6330,6 +6330,22 @@ comptime {
TODO right now bool is not accepted. Also I think we could make non powers of 2 work fine, maybe TODO right now bool is not accepted. Also I think we could make non powers of 2 work fine, maybe
we can remove this restriction we can remove this restriction
</p> </p>
<p>
Supported operations:
</p>
<ul>
<li>{#syntax#}.Xchg{#endsyntax#} - stores the operand unmodified.</li>
<li>{#syntax#}.Add{#endsyntax#} - for integers, twos complement wraparound addition.
Also supports {#link|Floats#}.</li>
<li>{#syntax#}.Sub{#endsyntax#} - for integers, twos complement wraparound subtraction.
Also supports {#link|Floats#}.</li>
<li>{#syntax#}.And{#endsyntax#} - bitwise and</li>
<li>{#syntax#}.Nand{#endsyntax#} - bitwise nand</li>
<li>{#syntax#}.Or{#endsyntax#} - bitwise or</li>
<li>{#syntax#}.Xor{#endsyntax#} - bitwise xor</li>
<li>{#syntax#}.Max{#endsyntax#} - stores the operand if it is larger. Supports integers and floats.</li>
<li>{#syntax#}.Min{#endsyntax#} - stores the operand if it is smaller. Supports integers and floats.</li>
</ul>
{#header_close#} {#header_close#}
{#header_open|@bitCast#} {#header_open|@bitCast#}
<pre>{#syntax#}@bitCast(comptime DestType: type, value: var) DestType{#endsyntax#}</pre> <pre>{#syntax#}@bitCast(comptime DestType: type, value: var) DestType{#endsyntax#}</pre>

View File

@ -805,11 +805,7 @@ pub const Builder = struct {
return name; return name;
} }
const full_path = try fs.path.join(self.allocator, [_][]const u8{ search_prefix, "bin", self.fmt("{}{}", name, exe_extension) }); const full_path = try fs.path.join(self.allocator, [_][]const u8{ search_prefix, "bin", self.fmt("{}{}", name, exe_extension) });
if (fs.path.real(self.allocator, full_path)) |real_path| { return fs.realpathAlloc(self.allocator, full_path) catch continue;
return real_path;
} else |_| {
continue;
}
} }
} }
if (self.env_map.get("PATH")) |PATH| { if (self.env_map.get("PATH")) |PATH| {
@ -817,14 +813,10 @@ pub const Builder = struct {
if (fs.path.isAbsolute(name)) { if (fs.path.isAbsolute(name)) {
return name; return name;
} }
var it = mem.tokenize(PATH, []u8{fs.path.delimiter}); var it = mem.tokenize(PATH, [_]u8{fs.path.delimiter});
while (it.next()) |path| { while (it.next()) |path| {
const full_path = try fs.path.join(self.allocator, [_][]const u8{ path, self.fmt("{}{}", name, exe_extension) }); const full_path = try fs.path.join(self.allocator, [_][]const u8{ path, self.fmt("{}{}", name, exe_extension) });
if (fs.path.real(self.allocator, full_path)) |real_path| { return fs.realpathAlloc(self.allocator, full_path) catch continue;
return real_path;
} else |_| {
continue;
}
} }
} }
} }
@ -834,11 +826,7 @@ pub const Builder = struct {
} }
for (paths) |path| { for (paths) |path| {
const full_path = try fs.path.join(self.allocator, [_][]const u8{ path, self.fmt("{}{}", name, exe_extension) }); const full_path = try fs.path.join(self.allocator, [_][]const u8{ path, self.fmt("{}{}", name, exe_extension) });
if (fs.path.real(self.allocator, full_path)) |real_path| { return fs.realpathAlloc(self.allocator, full_path) catch continue;
return real_path;
} else |_| {
continue;
}
} }
} }
return error.FileNotFound; return error.FileNotFound;
@ -904,6 +892,15 @@ pub const Builder = struct {
} }
}; };
test "builder.findProgram compiles" {
//allocator: *Allocator,
//zig_exe: []const u8,
//build_root: []const u8,
//cache_root: []const u8,
const builder = try Builder.create(std.heap.direct_allocator, "zig", "zig-cache", "zig-cache");
_ = builder.findProgram([_][]const u8{}, [_][]const u8{}) catch null;
}
pub const Version = struct { pub const Version = struct {
major: u32, major: u32,
minor: u32, minor: u32,
@ -1122,6 +1119,9 @@ pub const Target = union(enum) {
} }
pub fn libPrefix(self: Target) []const u8 { pub fn libPrefix(self: Target) []const u8 {
if (self.isWasm()) {
return "";
}
switch (self.getAbi()) { switch (self.getAbi()) {
.msvc => return "", .msvc => return "",
else => return "lib", else => return "lib",

View File

@ -1024,8 +1024,7 @@ pub fn openElfDebugInfo(
elf_seekable_stream: *DwarfSeekableStream, elf_seekable_stream: *DwarfSeekableStream,
elf_in_stream: *DwarfInStream, elf_in_stream: *DwarfInStream,
) !DwarfInfo { ) !DwarfInfo {
var efile: elf.Elf = undefined; var efile = try elf.Elf.openStream(allocator, elf_seekable_stream, elf_in_stream);
try efile.openStream(allocator, elf_seekable_stream, elf_in_stream);
errdefer efile.close(); errdefer efile.close();
var di = DwarfInfo{ var di = DwarfInfo{

View File

@ -356,7 +356,6 @@ pub const SectionHeader = struct {
pub const Elf = struct { pub const Elf = struct {
seekable_stream: *io.SeekableStream(anyerror, anyerror), seekable_stream: *io.SeekableStream(anyerror, anyerror),
in_stream: *io.InStream(anyerror), in_stream: *io.InStream(anyerror),
auto_close_stream: bool,
is_64: bool, is_64: bool,
endian: builtin.Endian, endian: builtin.Endian,
file_type: FileType, file_type: FileType,
@ -368,25 +367,23 @@ pub const Elf = struct {
string_section: *SectionHeader, string_section: *SectionHeader,
section_headers: []SectionHeader, section_headers: []SectionHeader,
allocator: *mem.Allocator, allocator: *mem.Allocator,
prealloc_file: File,
/// Call close when done. /// Call close when done.
pub fn openPath(elf: *Elf, allocator: *mem.Allocator, path: []const u8) !void { pub fn openPath(allocator: *mem.Allocator, path: []const u8) !Elf {
@compileError("TODO implement"); @compileError("TODO implement");
} }
/// Call close when done. /// Call close when done.
pub fn openFile(elf: *Elf, allocator: *mem.Allocator, file: File) !void { pub fn openFile(allocator: *mem.Allocator, file: File) !Elf {
@compileError("TODO implement"); @compileError("TODO implement");
} }
pub fn openStream( pub fn openStream(
elf: *Elf,
allocator: *mem.Allocator, allocator: *mem.Allocator,
seekable_stream: *io.SeekableStream(anyerror, anyerror), seekable_stream: *io.SeekableStream(anyerror, anyerror),
in: *io.InStream(anyerror), in: *io.InStream(anyerror),
) !void { ) !Elf {
elf.auto_close_stream = false; var elf: Elf = undefined;
elf.allocator = allocator; elf.allocator = allocator;
elf.seekable_stream = seekable_stream; elf.seekable_stream = seekable_stream;
elf.in_stream = in; elf.in_stream = in;
@ -523,12 +520,12 @@ pub const Elf = struct {
// not a string table // not a string table
return error.InvalidFormat; return error.InvalidFormat;
} }
return elf;
} }
pub fn close(elf: *Elf) void { pub fn close(elf: *Elf) void {
elf.allocator.free(elf.section_headers); elf.allocator.free(elf.section_headers);
if (elf.auto_close_stream) elf.prealloc_file.close();
} }
pub fn findSection(elf: *Elf, name: []const u8) !?*SectionHeader { pub fn findSection(elf: *Elf, name: []const u8) !?*SectionHeader {

View File

@ -1,6 +1,9 @@
const adler = @import("hash/adler.zig"); const adler = @import("hash/adler.zig");
pub const Adler32 = adler.Adler32; pub const Adler32 = adler.Adler32;
const auto_hash = @import("hash/auto_hash.zig");
pub const autoHash = auto_hash.autoHash;
// pub for polynomials + generic crc32 construction // pub for polynomials + generic crc32 construction
pub const crc = @import("hash/crc.zig"); pub const crc = @import("hash/crc.zig");
pub const Crc32 = crc.Crc32; pub const Crc32 = crc.Crc32;
@ -16,6 +19,8 @@ pub const SipHash128 = siphash.SipHash128;
pub const murmur = @import("hash/murmur.zig"); pub const murmur = @import("hash/murmur.zig");
pub const Murmur2_32 = murmur.Murmur2_32; pub const Murmur2_32 = murmur.Murmur2_32;
pub const Murmur2_64 = murmur.Murmur2_64; pub const Murmur2_64 = murmur.Murmur2_64;
pub const Murmur3_32 = murmur.Murmur3_32; pub const Murmur3_32 = murmur.Murmur3_32;
@ -23,11 +28,16 @@ pub const cityhash = @import("hash/cityhash.zig");
pub const CityHash32 = cityhash.CityHash32; pub const CityHash32 = cityhash.CityHash32;
pub const CityHash64 = cityhash.CityHash64; pub const CityHash64 = cityhash.CityHash64;
const wyhash = @import("hash/wyhash.zig");
pub const Wyhash = wyhash.Wyhash;
test "hash" { test "hash" {
_ = @import("hash/adler.zig"); _ = @import("hash/adler.zig");
_ = @import("hash/auto_hash.zig");
_ = @import("hash/crc.zig"); _ = @import("hash/crc.zig");
_ = @import("hash/fnv.zig"); _ = @import("hash/fnv.zig");
_ = @import("hash/siphash.zig"); _ = @import("hash/siphash.zig");
_ = @import("hash/murmur.zig"); _ = @import("hash/murmur.zig");
_ = @import("hash/cityhash.zig"); _ = @import("hash/cityhash.zig");
_ = @import("hash/wyhash.zig");
} }

211
std/hash/auto_hash.zig Normal file
View File

@ -0,0 +1,211 @@
const std = @import("std");
const builtin = @import("builtin");
const mem = std.mem;
const meta = std.meta;
/// Provides generic hashing for any eligible type.
/// Only hashes `key` itself, pointers are not followed.
pub fn autoHash(hasher: var, key: var) void {
const Key = @typeOf(key);
switch (@typeInfo(Key)) {
.NoReturn,
.Opaque,
.Undefined,
.ArgTuple,
.Void,
.Null,
.BoundFn,
.ComptimeFloat,
.ComptimeInt,
.Type,
.EnumLiteral,
.Frame,
=> @compileError("cannot hash this type"),
// Help the optimizer see that hashing an int is easy by inlining!
// TODO Check if the situation is better after #561 is resolved.
.Int => @inlineCall(hasher.update, std.mem.asBytes(&key)),
.Float => |info| autoHash(hasher, @bitCast(@IntType(false, info.bits), key)),
.Bool => autoHash(hasher, @boolToInt(key)),
.Enum => autoHash(hasher, @enumToInt(key)),
.ErrorSet => autoHash(hasher, @errorToInt(key)),
.AnyFrame, .Fn => autoHash(hasher, @ptrToInt(key)),
.Pointer => |info| switch (info.size) {
builtin.TypeInfo.Pointer.Size.One,
builtin.TypeInfo.Pointer.Size.Many,
builtin.TypeInfo.Pointer.Size.C,
=> autoHash(hasher, @ptrToInt(key)),
builtin.TypeInfo.Pointer.Size.Slice => {
autoHash(hasher, key.ptr);
autoHash(hasher, key.len);
},
},
.Optional => if (key) |k| autoHash(hasher, k),
.Array => {
// TODO detect via a trait when Key has no padding bits to
// hash it as an array of bytes.
// Otherwise, hash every element.
for (key) |element| {
autoHash(hasher, element);
}
},
.Vector => |info| {
if (info.child.bit_count % 8 == 0) {
// If there's no unused bits in the child type, we can just hash
// this as an array of bytes.
hasher.update(mem.asBytes(&key));
} else {
// Otherwise, hash every element.
// TODO remove the copy to an array once field access is done.
const array: [info.len]info.child = key;
comptime var i: u32 = 0;
inline while (i < info.len) : (i += 1) {
autoHash(hasher, array[i]);
}
}
},
.Struct => |info| {
// TODO detect via a trait when Key has no padding bits to
// hash it as an array of bytes.
// Otherwise, hash every field.
inline for (info.fields) |field| {
// We reuse the hash of the previous field as the seed for the
// next one so that they're dependant.
autoHash(hasher, @field(key, field.name));
}
},
.Union => |info| blk: {
if (info.tag_type) |tag_type| {
const tag = meta.activeTag(key);
const s = autoHash(hasher, tag);
inline for (info.fields) |field| {
const enum_field = field.enum_field.?;
if (enum_field.value == @enumToInt(tag)) {
autoHash(hasher, @field(key, enum_field.name));
// TODO use a labelled break when it does not crash the compiler.
// break :blk;
return;
}
}
unreachable;
} else @compileError("cannot hash untagged union type: " ++ @typeName(Key) ++ ", provide your own hash function");
},
.ErrorUnion => blk: {
const payload = key catch |err| {
autoHash(hasher, err);
break :blk;
};
autoHash(hasher, payload);
},
}
}
const testing = std.testing;
const Wyhash = std.hash.Wyhash;
fn testAutoHash(key: var) u64 {
// Any hash could be used here, for testing autoHash.
var hasher = Wyhash.init(0);
autoHash(&hasher, key);
return hasher.final();
}
test "autoHash slice" {
// Allocate one array dynamically so that we're assured it is not merged
// with the other by the optimization passes.
const array1 = try std.heap.direct_allocator.create([6]u32);
defer std.heap.direct_allocator.destroy(array1);
array1.* = [_]u32{ 1, 2, 3, 4, 5, 6 };
const array2 = [_]u32{ 1, 2, 3, 4, 5, 6 };
const a = array1[0..];
const b = array2[0..];
const c = array1[0..3];
testing.expect(testAutoHash(a) == testAutoHash(a));
testing.expect(testAutoHash(a) != testAutoHash(array1));
testing.expect(testAutoHash(a) != testAutoHash(b));
testing.expect(testAutoHash(a) != testAutoHash(c));
}
test "testAutoHash optional" {
const a: ?u32 = 123;
const b: ?u32 = null;
testing.expectEqual(testAutoHash(a), testAutoHash(u32(123)));
testing.expect(testAutoHash(a) != testAutoHash(b));
testing.expectEqual(testAutoHash(b), 0);
}
test "testAutoHash array" {
const a = [_]u32{ 1, 2, 3 };
const h = testAutoHash(a);
var hasher = Wyhash.init(0);
autoHash(&hasher, u32(1));
autoHash(&hasher, u32(2));
autoHash(&hasher, u32(3));
testing.expectEqual(h, hasher.final());
}
test "testAutoHash struct" {
const Foo = struct {
a: u32 = 1,
b: u32 = 2,
c: u32 = 3,
};
const f = Foo{};
const h = testAutoHash(f);
var hasher = Wyhash.init(0);
autoHash(&hasher, u32(1));
autoHash(&hasher, u32(2));
autoHash(&hasher, u32(3));
testing.expectEqual(h, hasher.final());
}
test "testAutoHash union" {
const Foo = union(enum) {
A: u32,
B: f32,
C: u32,
};
const a = Foo{ .A = 18 };
var b = Foo{ .B = 12.34 };
const c = Foo{ .C = 18 };
testing.expect(testAutoHash(a) == testAutoHash(a));
testing.expect(testAutoHash(a) != testAutoHash(b));
testing.expect(testAutoHash(a) != testAutoHash(c));
b = Foo{ .A = 18 };
testing.expect(testAutoHash(a) == testAutoHash(b));
}
test "testAutoHash vector" {
const a: @Vector(4, u32) = [_]u32{ 1, 2, 3, 4 };
const b: @Vector(4, u32) = [_]u32{ 1, 2, 3, 5 };
const c: @Vector(4, u31) = [_]u31{ 1, 2, 3, 4 };
testing.expect(testAutoHash(a) == testAutoHash(a));
testing.expect(testAutoHash(a) != testAutoHash(b));
testing.expect(testAutoHash(a) != testAutoHash(c));
}
test "testAutoHash error union" {
const Errors = error{Test};
const Foo = struct {
a: u32 = 1,
b: u32 = 2,
c: u32 = 3,
};
const f = Foo{};
const g: Errors!Foo = Errors.Test;
testing.expect(testAutoHash(f) != testAutoHash(g));
testing.expect(testAutoHash(f) == testAutoHash(Foo{}));
testing.expect(testAutoHash(g) == testAutoHash(Errors.Test));
}

View File

@ -0,0 +1,148 @@
const builtin = @import("builtin");
const std = @import("std");
const time = std.time;
const Timer = time.Timer;
const hash = std.hash;
const KiB = 1024;
const MiB = 1024 * KiB;
const GiB = 1024 * MiB;
var prng = std.rand.DefaultPrng.init(0);
const Hash = struct {
ty: type,
name: []const u8,
init_u8s: ?[]const u8 = null,
init_u64: ?u64 = null,
};
const siphash_key = "0123456789abcdef";
const hashes = [_]Hash{
Hash{ .ty = hash.Wyhash, .name = "wyhash", .init_u64 = 0 },
Hash{ .ty = hash.SipHash64(1, 3), .name = "siphash(1,3)", .init_u8s = siphash_key },
Hash{ .ty = hash.SipHash64(2, 4), .name = "siphash(2,4)", .init_u8s = siphash_key },
Hash{ .ty = hash.Fnv1a_64, .name = "fnv1a" },
Hash{ .ty = hash.Crc32, .name = "crc32" },
};
const Result = struct {
hash: u64,
throughput: u64,
};
pub fn benchmarkHash(comptime H: var, bytes: usize) !Result {
var h = blk: {
if (H.init_u8s) |init| {
break :blk H.ty.init(init);
}
if (H.init_u64) |init| {
break :blk H.ty.init(init);
}
break :blk H.ty.init();
};
var block: [8192]u8 = undefined;
prng.random.bytes(block[0..]);
var offset: usize = 0;
var timer = try Timer.start();
const start = timer.lap();
while (offset < bytes) : (offset += block.len) {
h.update(block[0..]);
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
return Result{
.hash = h.final(),
.throughput = throughput,
};
}
fn usage() void {
std.debug.warn(
\\throughput_test [options]
\\
\\Options:
\\ --filter [test-name]
\\ --seed [int]
\\ --count [int]
\\ --help
\\
);
}
fn mode(comptime x: comptime_int) comptime_int {
return if (builtin.mode == builtin.Mode.Debug) x / 64 else x;
}
// TODO(#1358): Replace with builtin formatted padding when available.
fn printPad(stdout: var, s: []const u8) !void {
var i: usize = 0;
while (i < 12 - s.len) : (i += 1) {
try stdout.print(" ");
}
try stdout.print("{}", s);
}
pub fn main() !void {
var stdout_file = try std.io.getStdOut();
var stdout_out_stream = stdout_file.outStream();
const stdout = &stdout_out_stream.stream;
var buffer: [1024]u8 = undefined;
var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]);
const args = try std.process.argsAlloc(&fixed.allocator);
var filter: ?[]u8 = "";
var count: usize = mode(128 * MiB);
var i: usize = 1;
while (i < args.len) : (i += 1) {
if (std.mem.eql(u8, args[i], "--seed")) {
i += 1;
if (i == args.len) {
usage();
std.os.exit(1);
}
const seed = try std.fmt.parseUnsigned(u32, args[i], 10);
prng.seed(seed);
} else if (std.mem.eql(u8, args[i], "--filter")) {
i += 1;
if (i == args.len) {
usage();
std.os.exit(1);
}
filter = args[i];
} else if (std.mem.eql(u8, args[i], "--count")) {
i += 1;
if (i == args.len) {
usage();
std.os.exit(1);
}
const c = try std.fmt.parseUnsigned(u32, args[i], 10);
count = c * MiB;
} else if (std.mem.eql(u8, args[i], "--help")) {
usage();
return;
} else {
usage();
std.os.exit(1);
}
}
inline for (hashes) |H| {
if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
const result = try benchmarkHash(H, count);
try printPad(stdout, H.name);
try stdout.print(": {:4} MiB/s [{:16}]\n", result.throughput / (1 * MiB), result.hash);
}
}
}

135
std/hash/wyhash.zig Normal file
View File

@ -0,0 +1,135 @@
const std = @import("std");
const mem = std.mem;
const primes = [_]u64{
0xa0761d6478bd642f,
0xe7037ed1a0b428db,
0x8ebc6af09c88c6e3,
0x589965cc75374cc3,
0x1d8e4e27c47d124f,
};
fn read_bytes(comptime bytes: u8, data: []const u8) u64 {
return mem.readVarInt(u64, data[0..bytes], .Little);
}
fn read_8bytes_swapped(data: []const u8) u64 {
return (read_bytes(4, data) << 32 | read_bytes(4, data[4..]));
}
fn mum(a: u64, b: u64) u64 {
var r = std.math.mulWide(u64, a, b);
r = (r >> 64) ^ r;
return @truncate(u64, r);
}
fn mix0(a: u64, b: u64, seed: u64) u64 {
return mum(a ^ seed ^ primes[0], b ^ seed ^ primes[1]);
}
fn mix1(a: u64, b: u64, seed: u64) u64 {
return mum(a ^ seed ^ primes[2], b ^ seed ^ primes[3]);
}
pub const Wyhash = struct {
seed: u64,
msg_len: usize,
pub fn init(seed: u64) Wyhash {
return Wyhash{
.seed = seed,
.msg_len = 0,
};
}
fn round(self: *Wyhash, b: []const u8) void {
std.debug.assert(b.len == 32);
self.seed = mix0(
read_bytes(8, b[0..]),
read_bytes(8, b[8..]),
self.seed,
) ^ mix1(
read_bytes(8, b[16..]),
read_bytes(8, b[24..]),
self.seed,
);
}
fn partial(self: *Wyhash, b: []const u8) void {
const rem_key = b;
const rem_len = b.len;
var seed = self.seed;
seed = switch (@intCast(u5, rem_len)) {
0 => seed,
1 => mix0(read_bytes(1, rem_key), primes[4], seed),
2 => mix0(read_bytes(2, rem_key), primes[4], seed),
3 => mix0((read_bytes(2, rem_key) << 8) | read_bytes(1, rem_key[2..]), primes[4], seed),
4 => mix0(read_bytes(4, rem_key), primes[4], seed),
5 => mix0((read_bytes(4, rem_key) << 8) | read_bytes(1, rem_key[4..]), primes[4], seed),
6 => mix0((read_bytes(4, rem_key) << 16) | read_bytes(2, rem_key[4..]), primes[4], seed),
7 => mix0((read_bytes(4, rem_key) << 24) | (read_bytes(2, rem_key[4..]) << 8) | read_bytes(1, rem_key[6..]), primes[4], seed),
8 => mix0(read_8bytes_swapped(rem_key), primes[4], seed),
9 => mix0(read_8bytes_swapped(rem_key), read_bytes(1, rem_key[8..]), seed),
10 => mix0(read_8bytes_swapped(rem_key), read_bytes(2, rem_key[8..]), seed),
11 => mix0(read_8bytes_swapped(rem_key), (read_bytes(2, rem_key[8..]) << 8) | read_bytes(1, rem_key[10..]), seed),
12 => mix0(read_8bytes_swapped(rem_key), read_bytes(4, rem_key[8..]), seed),
13 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 8) | read_bytes(1, rem_key[12..]), seed),
14 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 16) | read_bytes(2, rem_key[12..]), seed),
15 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 24) | (read_bytes(2, rem_key[12..]) << 8) | read_bytes(1, rem_key[14..]), seed),
16 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed),
17 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(1, rem_key[16..]), primes[4], seed),
18 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(2, rem_key[16..]), primes[4], seed),
19 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(2, rem_key[16..]) << 8) | read_bytes(1, rem_key[18..]), primes[4], seed),
20 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(4, rem_key[16..]), primes[4], seed),
21 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 8) | read_bytes(1, rem_key[20..]), primes[4], seed),
22 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 16) | read_bytes(2, rem_key[20..]), primes[4], seed),
23 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 24) | (read_bytes(2, rem_key[20..]) << 8) | read_bytes(1, rem_key[22..]), primes[4], seed),
24 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), primes[4], seed),
25 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(1, rem_key[24..]), seed),
26 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(2, rem_key[24..]), seed),
27 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(2, rem_key[24..]) << 8) | read_bytes(1, rem_key[26..]), seed),
28 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(4, rem_key[24..]), seed),
29 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 8) | read_bytes(1, rem_key[28..]), seed),
30 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 16) | read_bytes(2, rem_key[28..]), seed),
31 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 24) | (read_bytes(2, rem_key[28..]) << 8) | read_bytes(1, rem_key[30..]), seed),
};
self.seed = seed;
}
pub fn update(self: *Wyhash, b: []const u8) void {
var off: usize = 0;
// Full middle blocks.
while (off + 32 <= b.len) : (off += 32) {
@inlineCall(self.round, b[off .. off + 32]);
}
self.partial(b[off..]);
self.msg_len += b.len;
}
pub fn final(self: *Wyhash) u64 {
return mum(self.seed ^ self.msg_len, primes[4]);
}
pub fn hash(seed: u64, input: []const u8) u64 {
var c = Wyhash.init(seed);
c.update(input);
return c.final();
}
};
test "test vectors" {
const expectEqual = std.testing.expectEqual;
const hash = Wyhash.hash;
expectEqual(hash(0, ""), 0x0);
expectEqual(hash(1, "a"), 0xbed235177f41d328);
expectEqual(hash(2, "abc"), 0xbe348debe59b27c3);
expectEqual(hash(3, "message digest"), 0x37320f657213a290);
expectEqual(hash(4, "abcdefghijklmnopqrstuvwxyz"), 0xd0b270e1d8a7019c);
expectEqual(hash(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x602a1894d3bbfe7f);
expectEqual(hash(6, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x829e9c148b75970e);
}

View File

@ -4,6 +4,9 @@ const assert = debug.assert;
const testing = std.testing; const testing = std.testing;
const math = std.math; const math = std.math;
const mem = std.mem; const mem = std.mem;
const meta = std.meta;
const autoHash = std.hash.autoHash;
const Wyhash = std.hash.Wyhash;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const builtin = @import("builtin"); const builtin = @import("builtin");
@ -448,15 +451,17 @@ test "iterator hash map" {
try reset_map.putNoClobber(2, 22); try reset_map.putNoClobber(2, 22);
try reset_map.putNoClobber(3, 33); try reset_map.putNoClobber(3, 33);
// TODO this test depends on the hashing algorithm, because it assumes the
// order of the elements in the hashmap. This should not be the case.
var keys = [_]i32{ var keys = [_]i32{
1,
3, 3,
2, 2,
1,
}; };
var values = [_]i32{ var values = [_]i32{
11,
33, 33,
22, 22,
11,
}; };
var it = reset_map.iterator(); var it = reset_map.iterator();
@ -518,8 +523,9 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) {
pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
return struct { return struct {
fn hash(key: K) u32 { fn hash(key: K) u32 {
comptime var rng = comptime std.rand.DefaultPrng.init(0); var hasher = Wyhash.init(0);
return autoHash(key, &rng.random, u32); autoHash(&hasher, key);
return @truncate(u32, hasher.final());
} }
}.hash; }.hash;
} }
@ -527,116 +533,7 @@ pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
return struct { return struct {
fn eql(a: K, b: K) bool { fn eql(a: K, b: K) bool {
return autoEql(a, b); return meta.eql(a, b);
} }
}.eql; }.eql;
} }
// TODO improve these hash functions
pub fn autoHash(key: var, comptime rng: *std.rand.Random, comptime HashInt: type) HashInt {
switch (@typeInfo(@typeOf(key))) {
.NoReturn,
.Opaque,
.Undefined,
.ArgTuple,
.Frame,
.AnyFrame,
=> @compileError("cannot hash this type"),
.Void,
.Null,
=> return 0,
.Int => |info| {
const unsigned_x = @bitCast(@IntType(false, info.bits), key);
if (info.bits <= HashInt.bit_count) {
return HashInt(unsigned_x) ^ comptime rng.scalar(HashInt);
} else {
return @truncate(HashInt, unsigned_x ^ comptime rng.scalar(@typeOf(unsigned_x)));
}
},
.Float => |info| {
return autoHash(@bitCast(@IntType(false, info.bits), key), rng, HashInt);
},
.Bool => return autoHash(@boolToInt(key), rng, HashInt),
.Enum => return autoHash(@enumToInt(key), rng, HashInt),
.ErrorSet => return autoHash(@errorToInt(key), rng, HashInt),
.Fn => return autoHash(@ptrToInt(key), rng, HashInt),
.BoundFn,
.ComptimeFloat,
.ComptimeInt,
.Type,
.EnumLiteral,
=> return 0,
.Pointer => |info| switch (info.size) {
.One => @compileError("TODO auto hash for single item pointers"),
.Many => @compileError("TODO auto hash for many item pointers"),
.C => @compileError("TODO auto hash C pointers"),
.Slice => {
const interval = std.math.max(1, key.len / 256);
var i: usize = 0;
var h = comptime rng.scalar(HashInt);
while (i < key.len) : (i += interval) {
h ^= autoHash(key[i], rng, HashInt);
}
return h;
},
},
.Optional => @compileError("TODO auto hash for optionals"),
.Array => @compileError("TODO auto hash for arrays"),
.Vector => @compileError("TODO auto hash for vectors"),
.Struct => @compileError("TODO auto hash for structs"),
.Union => @compileError("TODO auto hash for unions"),
.ErrorUnion => @compileError("TODO auto hash for unions"),
}
}
pub fn autoEql(a: var, b: @typeOf(a)) bool {
switch (@typeInfo(@typeOf(a))) {
.NoReturn,
.Opaque,
.Undefined,
.ArgTuple,
=> @compileError("cannot test equality of this type"),
.Void,
.Null,
=> return true,
.Bool,
.Int,
.Float,
.ComptimeFloat,
.ComptimeInt,
.EnumLiteral,
.Promise,
.Enum,
.BoundFn,
.Fn,
.ErrorSet,
.Type,
=> return a == b,
.Pointer => |info| switch (info.size) {
.One => @compileError("TODO auto eql for single item pointers"),
.Many => @compileError("TODO auto eql for many item pointers"),
.C => @compileError("TODO auto eql for C pointers"),
.Slice => {
if (a.len != b.len) return false;
for (a) |a_item, i| {
if (!autoEql(a_item, b[i])) return false;
}
return true;
},
},
.Optional => @compileError("TODO auto eql for optionals"),
.Array => @compileError("TODO auto eql for arrays"),
.Struct => @compileError("TODO auto eql for structs"),
.Union => @compileError("TODO auto eql for unions"),
.ErrorUnion => @compileError("TODO auto eql for unions"),
.Vector => @compileError("TODO auto eql for vectors"),
}
}

View File

@ -102,9 +102,19 @@ test "HeaderEntry" {
testing.expectEqualSlices(u8, "x", e.value); testing.expectEqualSlices(u8, "x", e.value);
} }
fn stringEql(a: []const u8, b: []const u8) bool {
if (a.len != b.len) return false;
if (a.ptr == b.ptr) return true;
return mem.compare(u8, a, b) == .Equal;
}
fn stringHash(s: []const u8) u32 {
return @truncate(u32, std.hash.Wyhash.hash(0, s));
}
const HeaderList = std.ArrayList(HeaderEntry); const HeaderList = std.ArrayList(HeaderEntry);
const HeaderIndexList = std.ArrayList(usize); const HeaderIndexList = std.ArrayList(usize);
const HeaderIndex = std.AutoHashMap([]const u8, HeaderIndexList); const HeaderIndex = std.HashMap([]const u8, HeaderIndexList, stringHash, stringEql);
pub const Headers = struct { pub const Headers = struct {
// the owned header field name is stored in the index as part of the key // the owned header field name is stored in the index as part of the key

View File

@ -133,6 +133,11 @@ fn getRandomBytesDevURandom(buf: []u8) !void {
const fd = try openC(c"/dev/urandom", O_RDONLY | O_CLOEXEC, 0); const fd = try openC(c"/dev/urandom", O_RDONLY | O_CLOEXEC, 0);
defer close(fd); defer close(fd);
const st = try fstat(fd);
if (!S_ISCHR(st.mode)) {
return error.NoDevice;
}
const stream = &std.fs.File.openHandle(fd).inStream().stream; const stream = &std.fs.File.openHandle(fd).inStream().stream;
stream.readNoEof(buf) catch return error.Unexpected; stream.readNoEof(buf) catch return error.Unexpected;
} }

View File

@ -1116,3 +1116,62 @@ pub const stack_t = extern struct {
ss_size: isize, ss_size: isize,
ss_flags: i32, ss_flags: i32,
}; };
pub const S_IFMT = 0o170000;
pub const S_IFIFO = 0o010000;
pub const S_IFCHR = 0o020000;
pub const S_IFDIR = 0o040000;
pub const S_IFBLK = 0o060000;
pub const S_IFREG = 0o100000;
pub const S_IFLNK = 0o120000;
pub const S_IFSOCK = 0o140000;
pub const S_IFWHT = 0o160000;
pub const S_ISUID = 0o4000;
pub const S_ISGID = 0o2000;
pub const S_ISVTX = 0o1000;
pub const S_IRWXU = 0o700;
pub const S_IRUSR = 0o400;
pub const S_IWUSR = 0o200;
pub const S_IXUSR = 0o100;
pub const S_IRWXG = 0o070;
pub const S_IRGRP = 0o040;
pub const S_IWGRP = 0o020;
pub const S_IXGRP = 0o010;
pub const S_IRWXO = 0o007;
pub const S_IROTH = 0o004;
pub const S_IWOTH = 0o002;
pub const S_IXOTH = 0o001;
pub fn S_ISFIFO(m: u32) bool {
return m & S_IFMT == S_IFIFO;
}
pub fn S_ISCHR(m: u32) bool {
return m & S_IFMT == S_IFCHR;
}
pub fn S_ISDIR(m: u32) bool {
return m & S_IFMT == S_IFDIR;
}
pub fn S_ISBLK(m: u32) bool {
return m & S_IFMT == S_IFBLK;
}
pub fn S_ISREG(m: u32) bool {
return m & S_IFMT == S_IFREG;
}
pub fn S_ISLNK(m: u32) bool {
return m & S_IFMT == S_IFLNK;
}
pub fn S_ISSOCK(m: u32) bool {
return m & S_IFMT == S_IFSOCK;
}
pub fn S_IWHT(m: u32) bool {
return m & S_IFMT == S_IFWHT;
}

View File

@ -876,3 +876,62 @@ pub const stack_t = extern struct {
ss_size: isize, ss_size: isize,
ss_flags: i32, ss_flags: i32,
}; };
pub const S_IFMT = 0o170000;
pub const S_IFIFO = 0o010000;
pub const S_IFCHR = 0o020000;
pub const S_IFDIR = 0o040000;
pub const S_IFBLK = 0o060000;
pub const S_IFREG = 0o100000;
pub const S_IFLNK = 0o120000;
pub const S_IFSOCK = 0o140000;
pub const S_IFWHT = 0o160000;
pub const S_ISUID = 0o4000;
pub const S_ISGID = 0o2000;
pub const S_ISVTX = 0o1000;
pub const S_IRWXU = 0o700;
pub const S_IRUSR = 0o400;
pub const S_IWUSR = 0o200;
pub const S_IXUSR = 0o100;
pub const S_IRWXG = 0o070;
pub const S_IRGRP = 0o040;
pub const S_IWGRP = 0o020;
pub const S_IXGRP = 0o010;
pub const S_IRWXO = 0o007;
pub const S_IROTH = 0o004;
pub const S_IWOTH = 0o002;
pub const S_IXOTH = 0o001;
pub fn S_ISFIFO(m: u32) bool {
return m & S_IFMT == S_IFIFO;
}
pub fn S_ISCHR(m: u32) bool {
return m & S_IFMT == S_IFCHR;
}
pub fn S_ISDIR(m: u32) bool {
return m & S_IFMT == S_IFDIR;
}
pub fn S_ISBLK(m: u32) bool {
return m & S_IFMT == S_IFBLK;
}
pub fn S_ISREG(m: u32) bool {
return m & S_IFMT == S_IFREG;
}
pub fn S_ISLNK(m: u32) bool {
return m & S_IFMT == S_IFLNK;
}
pub fn S_ISSOCK(m: u32) bool {
return m & S_IFMT == S_IFSOCK;
}
pub fn S_IWHT(m: u32) bool {
return m & S_IFMT == S_IFWHT;
}

View File

@ -5,3 +5,4 @@ pub const is_the_target = switch (builtin.os) {
else => false, else => false,
}; };
pub usingnamespace std.c; pub usingnamespace std.c;
pub usingnamespace @import("bits.zig");

View File

@ -2,3 +2,4 @@ const std = @import("../std.zig");
const builtin = @import("builtin"); const builtin = @import("builtin");
pub const is_the_target = builtin.os == .freebsd; pub const is_the_target = builtin.os == .freebsd;
pub usingnamespace std.c; pub usingnamespace std.c;
pub usingnamespace @import("bits.zig");

View File

@ -234,10 +234,13 @@ pub const Tree = struct {
return null; return null;
} }
/// lookup searches for the value of key, using binary search. It will
/// return a pointer to the node if it is there, otherwise it will return null.
/// Complexity guaranteed O(log n), where n is the number of nodes book-kept
/// by tree.
pub fn lookup(tree: *Tree, key: *Node) ?*Node { pub fn lookup(tree: *Tree, key: *Node) ?*Node {
var parent: *Node = undefined; var parent: ?*Node = undefined;
var is_left: bool = undefined; var is_left: bool = undefined;
return doLookup(key, tree, &parent, &is_left); return doLookup(key, tree, &parent, &is_left);
} }
@ -545,3 +548,47 @@ test "rb" {
num = testGetNumber(num.node.next().?); num = testGetNumber(num.node.next().?);
} }
} }
test "inserting and looking up" {
var tree: Tree = undefined;
tree.init(testCompare);
var number: testNumber = undefined;
number.value = 1000;
_ = tree.insert(&number.node);
var dup: testNumber = undefined;
//Assert that tuples with identical value fields finds the same pointer
dup.value = 1000;
assert(tree.lookup(&dup.node) == &number.node);
//Assert that tuples with identical values do not clobber when inserted.
_ = tree.insert(&dup.node);
assert(tree.lookup(&dup.node) == &number.node);
assert(tree.lookup(&number.node) != &dup.node);
assert(testGetNumber(tree.lookup(&dup.node).?).value == testGetNumber(&dup.node).value);
//Assert that if looking for a non-existing value, return null.
var non_existing_value: testNumber = undefined;
non_existing_value.value = 1234;
assert(tree.lookup(&non_existing_value.node) == null);
}
test "multiple inserts, followed by calling first and last" {
var tree: Tree = undefined;
tree.init(testCompare);
var zeroth: testNumber = undefined;
zeroth.value = 0;
var first: testNumber = undefined;
first.value = 1;
var second: testNumber = undefined;
second.value = 2;
var third: testNumber = undefined;
third.value = 3;
_ = tree.insert(&zeroth.node);
_ = tree.insert(&first.node);
_ = tree.insert(&second.node);
_ = tree.insert(&third.node);
assert(testGetNumber(tree.first().?).value == 0);
assert(testGetNumber(tree.last().?).value == 3);
var lookupNode: testNumber = undefined;
lookupNode.value = 3;
assert(tree.lookup(&lookupNode.node) == &third.node);
}