aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-12-11 18:41:54 -0800
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-12-11 18:41:54 -0800
commitb746579863b0f67781c67031925a215816df19e8 (patch)
treec4f0358b6f0622e351af736859c993562bbdabe6 /src
parent8549134658960bb5b07a4e5f8da5c2abf61513ec (diff)
downloadbun-b746579863b0f67781c67031925a215816df19e8.tar.gz
bun-b746579863b0f67781c67031925a215816df19e8.tar.zst
bun-b746579863b0f67781c67031925a215816df19e8.zip
[internal] Change HashMap implementation for storing symbols
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/api/bun.zig1
-rw-r--r--src/bun.js/api/ffi.zig1
-rw-r--r--src/bun.js/api/server.zig1
-rw-r--r--src/bun.js/config.zig1
-rw-r--r--src/bun.js/javascript.zig1
-rw-r--r--src/bun.js/module_loader.zig1
-rw-r--r--src/bun.js/node_streams_consumer.exports.js2
-rw-r--r--src/bun.zig14
-rw-r--r--src/bundler.zig1
-rw-r--r--src/hash_map.zig1364
-rw-r--r--src/js_ast.zig36
-rw-r--r--src/js_parser.zig84
12 files changed, 94 insertions, 1413 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index ff8776c4b..7d7c0ec7a 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -24,7 +24,6 @@ const ServerEntryPoint = @import("../../bundler.zig").ServerEntryPoint;
const js_printer = @import("../../js_printer.zig");
const js_parser = @import("../../js_parser.zig");
const js_ast = @import("../../js_ast.zig");
-const hash_map = @import("../../hash_map.zig");
const http = @import("../../http.zig");
const NodeFallbackModules = @import("../../node_fallbacks.zig");
const ImportKind = ast.ImportKind;
diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig
index 520dbf743..6ea141559 100644
--- a/src/bun.js/api/ffi.zig
+++ b/src/bun.js/api/ffi.zig
@@ -24,7 +24,6 @@ const ServerEntryPoint = @import("../../bundler.zig").ServerEntryPoint;
const js_printer = @import("../../js_printer.zig");
const js_parser = @import("../../js_parser.zig");
const js_ast = @import("../../js_ast.zig");
-const hash_map = @import("../../hash_map.zig");
const http = @import("../../http.zig");
const NodeFallbackModules = @import("../../node_fallbacks.zig");
const ImportKind = ast.ImportKind;
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig
index a8216c689..6531584c0 100644
--- a/src/bun.js/api/server.zig
+++ b/src/bun.js/api/server.zig
@@ -24,7 +24,6 @@ const ServerEntryPoint = @import("../../bundler.zig").ServerEntryPoint;
const js_printer = @import("../../js_printer.zig");
const js_parser = @import("../../js_parser.zig");
const js_ast = @import("../../js_ast.zig");
-const hash_map = @import("../../hash_map.zig");
const http = @import("../../http.zig");
const NodeFallbackModules = @import("../../node_fallbacks.zig");
const ImportKind = ast.ImportKind;
diff --git a/src/bun.js/config.zig b/src/bun.js/config.zig
index 78f76d9a4..ee3e30412 100644
--- a/src/bun.js/config.zig
+++ b/src/bun.js/config.zig
@@ -19,7 +19,6 @@ const Api = @import("../api/schema.zig").Api;
const options = @import("../options.zig");
const Bundler = @import("../bundler.zig").ServeBundler;
const js_printer = @import("../js_printer.zig");
-const hash_map = @import("../hash_map.zig");
const http = @import("../http.zig");
pub const DefaultBunDefines = struct {
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index c9b165def..c0740b735 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -33,7 +33,6 @@ const ServerEntryPoint = @import("../bundler.zig").ServerEntryPoint;
const js_printer = @import("../js_printer.zig");
const js_parser = @import("../js_parser.zig");
const js_ast = @import("../js_ast.zig");
-const hash_map = @import("../hash_map.zig");
const http = @import("../http.zig");
const NodeFallbackModules = @import("../node_fallbacks.zig");
const ImportKind = ast.ImportKind;
diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig
index 65bda2165..65b2a87bf 100644
--- a/src/bun.js/module_loader.zig
+++ b/src/bun.js/module_loader.zig
@@ -33,7 +33,6 @@ const ServerEntryPoint = @import("../bundler.zig").ServerEntryPoint;
const js_printer = @import("../js_printer.zig");
const js_parser = @import("../js_parser.zig");
const js_ast = @import("../js_ast.zig");
-const hash_map = @import("../hash_map.zig");
const http = @import("../http.zig");
const NodeFallbackModules = @import("../node_fallbacks.zig");
const ImportKind = ast.ImportKind;
diff --git a/src/bun.js/node_streams_consumer.exports.js b/src/bun.js/node_streams_consumer.exports.js
index 6fa738076..d4f775ba9 100644
--- a/src/bun.js/node_streams_consumer.exports.js
+++ b/src/bun.js/node_streams_consumer.exports.js
@@ -1,3 +1,5 @@
+const { Bun } = import.meta.primordials;
+
export const arrayBuffer = Bun.readableStreamToArrayBuffer;
export const text = Bun.readableStreamToText;
export const json = (stream) =>
diff --git a/src/bun.zig b/src/bun.zig
index d96d87ea0..bb47a46f7 100644
--- a/src/bun.zig
+++ b/src/bun.zig
@@ -524,6 +524,20 @@ pub const StringHashMapContext = struct {
pub fn eql(_: @This(), a: []const u8, b: []const u8) bool {
return strings.eqlLong(a, b, true);
}
+
+ pub const Prehashed = struct {
+ value: u64,
+ input: []const u8,
+ pub fn hash(this: @This(), s: []const u8) u64 {
+ if (s.ptr == this.input.ptr and s.len == this.input.len)
+ return this.value;
+ return std.hash.Wyhash.hash(0, s);
+ }
+
+ pub fn eql(_: @This(), a: []const u8, b: []const u8) bool {
+ return strings.eqlLong(a, b, true);
+ }
+ };
};
pub fn StringArrayHashMap(comptime Type: type) type {
diff --git a/src/bundler.zig b/src/bundler.zig
index 0e21825cc..53cbccb91 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -34,7 +34,6 @@ const allocators = @import("./allocators.zig");
const MimeType = @import("./http/mime_type.zig");
const resolve_path = @import("./resolver/resolve_path.zig");
const runtime = @import("./runtime.zig");
-const hash_map = @import("hash_map.zig");
const PackageJSON = @import("./resolver/package_json.zig").PackageJSON;
const MacroRemap = @import("./resolver/package_json.zig").MacroMap;
const DebugLogs = _resolver.DebugLogs;
diff --git a/src/hash_map.zig b/src/hash_map.zig
deleted file mode 100644
index c16dad553..000000000
--- a/src/hash_map.zig
+++ /dev/null
@@ -1,1364 +0,0 @@
-// This is the previous version of the hash map implementation built in to zig
-// except, with a getWithHash(key: K)
-// and a hash getWithHash(key: K)
-// This improved JS parser performance due to being able to reuse hash keys when looking up symbol table entries
-
-const std = @import("std");
-const assert = debug.assert;
-const autoHash = std.hash.autoHash;
-const debug = std.debug;
-const warn = debug.warn;
-const math = std.math;
-const mem = std.mem;
-const meta = std.meta;
-const trait = meta.trait;
-const Allocator = mem.Allocator;
-const Wyhash = std.hash.Wyhash;
-
-pub fn getAutoHashFn(comptime K: type) (fn (K) u64) {
- comptime {
- assert(@hasDecl(std, "StringHashMap")); // detect when the following message needs updated
- if (K == []const u8) {
- @compileError("std.auto_hash.autoHash does not allow slices here (" ++
- @typeName(K) ++
- ") because the intent is unclear. " ++
- "Consider using bun.StringHashMap for hashing the contents of []const u8. " ++
- "Alternatively, consider using std.auto_hash.hash or providing your own hash function instead.");
- }
- }
-
- return struct {
- fn hash(key: K) u64 {
- if (comptime trait.hasUniqueRepresentation(K)) {
- return Wyhash.hash(0, std.mem.asBytes(&key));
- } else {
- var hasher = Wyhash.init(0);
- autoHash(&hasher, key);
- return hasher.final();
- }
- }
- }.hash;
-}
-
-const autoEqlTrait = std.meta.trait.hasFn("eql");
-pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
- if (comptime autoEqlTrait(K)) {
- return @field(K, "eql");
- }
-
- return struct {
- fn eql(a: K, b: K) bool {
- return meta.eql(a, b);
- }
- }.eql;
-}
-
-pub fn AutoHashMap(comptime K: type, comptime V: type) type {
- return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage);
-}
-
-pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type {
- return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage);
-}
-
-/// Builtin hashmap for strings as keys.
-pub fn StringHashMap(comptime V: type) type {
- return HashMap([]const u8, V, hashString, eqlString, default_max_load_percentage);
-}
-
-pub fn StringHashMapUnmanaged(comptime V: type) type {
- return HashMapUnmanaged([]const u8, V, hashString, eqlString, default_max_load_percentage);
-}
-
-// ❯ hyperfine "./bun.stringEqlNoPtrCheck --resolve=dev --cwd /Users/jarred/Code/bun/bench/rome/src entry --platform=node --outdir=/Users/jarred/Code/bun/bench/rome/src/out --origin=https://hello.com/" "./bun-fd-rel-hash --resolve=dev --cwd /Users/jarred/Code/bun/bench/rome/src entry --platform=node --outdir=/Users/jarred/Code/bun/bench/rome/src/out --origin=https://hello.com/" --min-runs=50
-// Benchmark #1: ./bun.stringEqlNoPtrCheck --resolve=dev --cwd /Users/jarred/Code/bun/bench/rome/src entry --platform=node --outdir=/Users/jarred/Code/bun/bench/rome/src/out --origin=https://hello.com/
-// Time (mean ± σ): 251.5 ms ± 7.8 ms [User: 110.0 ms, System: 135.7 ms]
-// Range (min … max): 239.0 ms … 281.1 ms 50 runs
-
-// Benchmark #2: ./bun-fd-rel-hash --resolve=dev --cwd /Users/jarred/Code/bun/bench/rome/src entry --platform=node --outdir=/Users/jarred/Code/bun/bench/rome/src/out --origin=https://hello.com/
-// Time (mean ± σ): 249.3 ms ± 8.3 ms [User: 110.8 ms, System: 134.0 ms]
-// Range (min … max): 239.8 ms … 288.7 ms 50 runs
-
-// Summary
-// './bun-fd-rel-hash --resolve=dev --cwd /Users/jarred/Code/bun/bench/rome/src entry --platform=node --outdir=/Users/jarred/Code/bun/bench/rome/src/out --origin=https://hello.com/' ran
-// 1.01 ± 0.05 times faster than './bun.stringEqlNoPtrCheck --resolve=dev --cwd /Users/jarred/Code/bun/bench/rome/src entry --platform=node --outdir=/Users/jarred/Code/bun/bench/rome/src/out --origin=https://hello.com/'
-pub fn eqlString(a: []const u8, b: []const u8) bool {
- return mem.eql(u8, a, b);
-}
-
-pub fn hashString(s: []const u8) u64 {
- return std.hash.Wyhash.hash(0, s);
-}
-
-/// Deprecated use `default_max_load_percentage`
-pub const DefaultMaxLoadPercentage = default_max_load_percentage;
-
-pub const default_max_load_percentage = 80;
-
-/// General purpose hash table.
-/// No order is guaranteed and any modification invalidates live iterators.
-/// It provides fast operations (lookup, insertion, deletion) with quite high
-/// load factors (up to 80% by default) for a low memory usage.
-/// For a hash map that can be initialized directly that does not store an Allocator
-/// field, see `HashMapUnmanaged`.
-/// If iterating over the table entries is a strong usecase and needs to be fast,
-/// prefer the alternative `std.ArrayHashMap`.
-pub fn HashMap(
- comptime K: type,
- comptime V: type,
- comptime hashFn: fn (key: K) u64,
- comptime eqlFn: fn (a: K, b: K) bool,
- comptime max_load_percentage: u64,
-) type {
- return struct {
- unmanaged: Unmanaged,
- allocator: Allocator,
-
- pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, max_load_percentage);
- pub const Entry = Unmanaged.Entry;
- pub const Hash = Unmanaged.Hash;
- pub const Iterator = Unmanaged.Iterator;
- pub const Size = Unmanaged.Size;
- pub const GetOrPutResult = Unmanaged.GetOrPutResult;
-
- const Self = @This();
-
- pub fn init(allocator: Allocator) Self {
- return .{
- .unmanaged = .{},
- .allocator = allocator,
- };
- }
-
- pub fn deinit(self: *Self) void {
- self.unmanaged.deinit(self.allocator);
- self.* = undefined;
- }
-
- pub fn clearRetainingCapacity(self: *Self) void {
- return self.unmanaged.clearRetainingCapacity();
- }
-
- pub fn clearAndFree(self: *Self) void {
- return self.unmanaged.clearAndFree(self.allocator);
- }
-
- pub fn count(self: Self) Size {
- return self.unmanaged.count();
- }
-
- pub fn iterator(self: *const Self) Iterator {
- return self.unmanaged.iterator();
- }
-
- /// Get an optional pointer to the value associated with key, if present.
- pub fn getHash(key: K) u64 {
- return hashFn(key);
- }
-
- /// If key exists this function cannot fail.
- /// If there is an existing item with `key`, then the result
- /// `Entry` pointer points to it, and found_existing is true.
- /// Otherwise, puts a new item with undefined value, and
- /// the `Entry` pointer points to it. Caller should then initialize
- /// the value (but not the key).
- pub fn getOrPut(self: *Self, key: K) !GetOrPutResult {
- return self.unmanaged.getOrPut(self.allocator, key);
- }
-
- /// If key exists this function cannot fail.
- /// If there is an existing item with `key`, then the result
- /// `Entry` pointer points to it, and found_existing is true.
- /// Otherwise, puts a new item with undefined value, and
- /// the `Entry` pointer points to it. Caller should then initialize
- /// the value (but not the key).
- pub fn getOrPutWithHash(self: *Self, key: K, hash: u64) !GetOrPutResult {
- return self.unmanaged.getOrPutWithHash(self.allocator, key, hash);
- }
-
- /// If there is an existing item with `key`, then the result
- /// `Entry` pointer points to it, and found_existing is true.
- /// Otherwise, puts a new item with undefined value, and
- /// the `Entry` pointer points to it. Caller should then initialize
- /// the value (but not the key).
- /// If a new entry needs to be stored, this function asserts there
- /// is enough capacity to store it.
- pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
- return self.unmanaged.getOrPutAssumeCapacity(key);
- }
-
- pub fn getOrPutValue(self: *Self, key: K, value: V) !*Entry {
- return self.unmanaged.getOrPutValue(self.allocator, key, value);
- }
-
- /// Increases capacity, guaranteeing that insertions up until the
- /// `expected_count` will not cause an allocation, and therefore cannot fail.
- pub fn ensureCapacity(self: *Self, expected_count: Size) !void {
- return self.unmanaged.ensureCapacity(self.allocator, expected_count);
- }
-
- /// Returns the number of total elements which may be present before it is
- /// no longer guaranteed that no allocations will be performed.
- pub fn capacity(self: *Self) Size {
- return self.unmanaged.capacity();
- }
-
- /// Clobbers any existing data. To detect if a put would clobber
- /// existing data, see `getOrPut`.
- pub fn put(self: *Self, key: K, value: V) !void {
- return self.unmanaged.put(self.allocator, key, value);
- }
-
- /// Clobbers any existing data. To detect if a put would clobber
- /// existing data, see `getOrPut`.
- pub fn putWithHash(self: *Self, key: K, hash: u64, value: V) !void {
- return self.unmanaged.putWithHash(self.allocator, key, hash, value);
- }
-
- /// Inserts a key-value pair into the hash map, asserting that no previous
- /// entry with the same key is already present
- pub fn putNoClobber(self: *Self, key: K, value: V) !void {
- return self.unmanaged.putNoClobber(self.allocator, key, value);
- }
-
- /// Asserts there is enough capacity to store the new key-value pair.
- /// Clobbers any existing data. To detect if a put would clobber
- /// existing data, see `getOrPutAssumeCapacity`.
- pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {
- return self.unmanaged.putAssumeCapacity(key, value);
- }
-
- /// Asserts there is enough capacity to store the new key-value pair.
- /// Asserts that it does not clobber any existing data.
- /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`.
- pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {
- return self.unmanaged.putAssumeCapacityNoClobber(key, value);
- }
-
- /// Asserts there is enough capacity to store the new key-value pair.
- /// Asserts that it does not clobber any existing data.
- /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`.
- pub fn putAssumeCapacityNoClobberWithHash(self: *Self, key: K, hash: u64, value: V) void {
- return self.unmanaged.putAssumeCapacityNoClobberWithHash(key, hash, value);
- }
-
- /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
- pub fn fetchPut(self: *Self, key: K, value: V) !?Entry {
- return self.unmanaged.fetchPut(self.allocator, key, value);
- }
-
- /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
- /// If insertion happuns, asserts there is enough capacity without allocating.
- pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry {
- return self.unmanaged.fetchPutAssumeCapacity(key, value);
- }
-
- pub fn get(self: Self, key: K) ?V {
- return self.unmanaged.get(key);
- }
-
- pub fn getWithHash(self: Self, key: K, hash: u64) ?V {
- return self.unmanaged.getWithHash(key, hash);
- }
-
- pub fn getEntry(self: Self, key: K) ?*Entry {
- return self.unmanaged.getEntry(key);
- }
-
- pub fn getEntryWithHash(self: Self, key: K, hash: u64) ?*Entry {
- return self.unmanaged.getEntryWithHash(key, hash);
- }
-
- pub fn contains(self: Self, key: K) bool {
- return self.unmanaged.contains(key);
- }
-
- /// If there is an `Entry` with a matching key, it is deleted from
- /// the hash map, and then returned from this function.
- pub fn remove(self: *Self, key: K) ?Entry {
- return self.unmanaged.remove(key);
- }
-
- /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
- /// and discards it.
- pub fn removeAssertDiscard(self: *Self, key: K) void {
- return self.unmanaged.removeAssertDiscard(key);
- }
-
- pub fn clone(self: Self) !Self {
- var other = try self.unmanaged.clone(self.allocator);
- return other.promote(self.allocator);
- }
- };
-}
-
-/// A HashMap based on open addressing and linear probing.
-/// A lookup or modification typically occurs only 2 cache misses.
-/// No order is guaranteed and any modification invalidates live iterators.
-/// It achieves good performance with quite high load factors (by default,
-/// grow is triggered at 80% full) and only one byte of overhead per element.
-/// The struct itself is only 16 bytes for a small footprint. This comes at
-/// the price of handling size with u32, which should be reasonnable enough
-/// for almost all uses.
-/// Deletions are achieved with tombstones.
-pub fn HashMapUnmanaged(
- comptime K: type,
- comptime V: type,
- hashFn: fn (key: K) u64,
- eqlFn: fn (a: K, b: K) bool,
- comptime max_load_percentage: u64,
-) type {
- comptime assert(max_load_percentage > 0 and max_load_percentage < 100);
-
- return struct {
- const Self = @This();
-
- // This is actually a midway pointer to the single buffer containing
- // a `Header` field, the `Metadata`s and `Entry`s.
- // At `-@sizeOf(Header)` is the Header field.
- // At `sizeOf(Metadata) * capacity + offset`, which is pointed to by
- // self.header().entries, is the array of entries.
- // This means that the hashmap only holds one live allocation, to
- // reduce memory fragmentation and struct size.
- /// Pointer to the metadata.
- metadata: ?[*]Metadata = null,
-
- /// Current number of elements in the hashmap.
- size: Size = 0,
-
- // Having a countdown to grow reduces the number of instructions to
- // execute when determining if the hashmap has enough capacity already.
- /// Number of available slots before a grow is needed to satisfy the
- /// `max_load_percentage`.
- available: Size = 0,
-
- // This is purely empirical and not a /very smart magic constant™/.
- /// Capacity of the first grow when bootstrapping the hashmap.
- const minimal_capacity = 8;
-
- // This hashmap is specially designed for sizes that fit in a u32.
- const Size = u32;
-
- // u64 hashes guarantee us that the fingerprint bits will never be used
- // to compute the index of a slot, maximizing the use of entropy.
- const Hash = u64;
-
- pub const Entry = struct {
- key: K,
- value: V,
- };
-
- const Header = packed struct {
- entries: [*]Entry,
- capacity: Size,
- };
-
- /// Metadata for a slot. It can be in three states: empty, used or
- /// tombstone. Tombstones indicate that an entry was previously used,
- /// they are a simple way to handle removal.
- /// To this state, we add 7 bits from the slot's key hash. These are
- /// used as a fast way to disambiguate between entries without
- /// having to use the equality function. If two fingerprints are
- /// different, we know that we don't have to compare the keys at all.
- /// The 7 bits are the highest ones from a 64 bit hash. This way, not
- /// only we use the `log2(capacity)` lowest bits from the hash to determine
- /// a slot index, but we use 7 more bits to quickly resolve collisions
- /// when multiple elements with different hashes end up wanting to be in the same slot.
- /// Not using the equality function means we don't have to read into
- /// the entries array, likely avoiding a cache miss and a potentially
- /// costly function call.
- const Metadata = packed struct {
- const FingerPrint = u7;
-
- const free: FingerPrint = 0;
- const tombstone: FingerPrint = 1;
-
- fingerprint: FingerPrint = free,
- used: u1 = 0,
-
- pub fn isUsed(self: Metadata) bool {
- return self.used == 1;
- }
-
- pub fn isTombstone(self: Metadata) bool {
- return !self.isUsed() and self.fingerprint == tombstone;
- }
-
- pub fn takeFingerprint(hash: Hash) FingerPrint {
- const hash_bits = @typeInfo(Hash).Int.bits;
- const fp_bits = @typeInfo(FingerPrint).Int.bits;
- return @truncate(FingerPrint, hash >> (hash_bits - fp_bits));
- }
-
- pub fn fill(self: *Metadata, fp: FingerPrint) void {
- self.used = 1;
- self.fingerprint = fp;
- }
-
- pub fn remove(self: *Metadata) void {
- self.used = 0;
- self.fingerprint = tombstone;
- }
- };
-
- comptime {
- assert(@sizeOf(Metadata) == 1);
- assert(@alignOf(Metadata) == 1);
- }
-
- const Iterator = struct {
- hm: *const Self,
- index: Size = 0,
-
- pub fn next(it: *Iterator) ?*Entry {
- if (@import("builtin").mode != .ReleaseFast) assert(it.index <= it.hm.capacity());
- if (it.hm.size == 0) return null;
-
- const cap = it.hm.capacity();
- const end = it.hm.metadata.? + cap;
- var metadata = it.hm.metadata.? + it.index;
-
- while (metadata != end) : ({
- metadata += 1;
- it.index += 1;
- }) {
- if (metadata[0].isUsed()) {
- const entry = &it.hm.entries()[it.index];
- it.index += 1;
- return entry;
- }
- }
-
- return null;
- }
- };
-
- pub const GetOrPutResult = struct {
- entry: *Entry,
- found_existing: bool,
- };
-
- pub const Managed = HashMap(K, V, hashFn, eqlFn, max_load_percentage);
-
- pub fn promote(self: Self, allocator: Allocator) Managed {
- return .{
- .unmanaged = self,
- .allocator = allocator,
- };
- }
-
- fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {
- return size * 100 < max_load_percentage * cap;
- }
-
- pub fn deinit(self: *Self, allocator: Allocator) void {
- self.deallocate(allocator);
- self.* = undefined;
- }
-
- fn deallocate(self: *Self, allocator: Allocator) void {
- if (self.metadata == null) return;
-
- const cap = self.capacity();
- const meta_size = @sizeOf(Header) + cap * @sizeOf(Metadata);
-
- const alignment = @alignOf(Entry) - 1;
- const entries_size = @as(usize, cap) * @sizeOf(Entry) + alignment;
-
- const total_size = meta_size + entries_size;
-
- var slice: []u8 = undefined;
- slice.ptr = @intToPtr([*]u8, @ptrToInt(self.header()));
- slice.len = total_size;
- allocator.free(slice);
-
- self.metadata = null;
- self.available = 0;
- }
-
- fn capacityForSize(size: Size) Size {
- var new_cap = @truncate(u32, (@as(u64, size) * 100) / max_load_percentage + 1);
- new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable;
- return new_cap;
- }
-
- pub fn ensureCapacity(self: *Self, allocator: Allocator, new_size: Size) !void {
- if (new_size > self.size)
- try self.growIfNeeded(allocator, new_size - self.size);
- }
-
- pub fn clearRetainingCapacity(self: *Self) void {
- if (self.metadata) |_| {
- self.initMetadatas();
- self.size = 0;
- self.available = @truncate(u32, (self.capacity() * max_load_percentage) / 100);
- }
- }
-
- pub fn clearAndFree(self: *Self, allocator: Allocator) void {
- self.deallocate(allocator);
- self.size = 0;
- self.available = 0;
- }
-
- pub fn count(self: *const Self) Size {
- return self.size;
- }
-
- fn header(self: *const Self) *Header {
- return @ptrCast(*Header, @ptrCast([*]Header, self.metadata.?) - 1);
- }
-
- fn entries(self: *const Self) [*]Entry {
- return self.header().entries;
- }
-
- pub fn capacity(self: *const Self) Size {
- if (self.metadata == null) return 0;
-
- return self.header().capacity;
- }
-
- pub fn iterator(self: *const Self) Iterator {
- return .{ .hm = self };
- }
-
- /// Insert an entry in the map. Assumes it is not already present.
- pub fn putNoClobber(self: *Self, allocator: Allocator, key: K, value: V) !void {
- if (@import("builtin").mode != .ReleaseFast) assert(!self.contains(key));
- try self.growIfNeeded(allocator, 1);
-
- self.putAssumeCapacityNoClobber(key, value);
- }
-
- /// Asserts there is enough capacity to store the new key-value pair.
- /// Clobbers any existing data. To detect if a put would clobber
- /// existing data, see `getOrPutAssumeCapacity`.
- pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {
- const gop = self.getOrPutAssumeCapacity(key);
- gop.entry.value = value;
- }
-
- /// Insert an entry in the map. Assumes it is not already present,
- /// and that no allocation is needed.
- pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {
- if (@import("builtin").mode != .ReleaseFast) assert(!self.contains(key));
-
- const hash = hashFn(key);
- putAssumeCapacityNoClobberWithHash(self, key, hash, value);
- }
-
- /// Insert an entry in the map. Assumes it is not already present,
- /// and that no allocation is needed.
- pub fn putAssumeCapacityNoClobberWithHash(self: *Self, key: K, hash: u64, value: V) void {
- const mask = self.capacity() - 1;
- var idx = @truncate(usize, hash & mask);
-
- var metadata = self.metadata.? + idx;
- while (metadata[0].isUsed()) {
- idx = (idx + 1) & mask;
- metadata = self.metadata.? + idx;
- }
-
- if (!metadata[0].isTombstone()) {
- if (@import("builtin").mode != .ReleaseFast) assert(self.available > 0);
- self.available -= 1;
- }
-
- const fingerprint = Metadata.takeFingerprint(hash);
- metadata[0].fill(fingerprint);
- self.entries()[idx] = Entry{ .key = key, .value = value };
-
- self.size += 1;
- }
-
- /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
- pub fn fetchPut(self: *Self, allocator: Allocator, key: K, value: V) !?Entry {
- const gop = try self.getOrPut(allocator, key);
- var result: ?Entry = null;
- if (gop.found_existing) {
- result = gop.entry.*;
- }
- gop.entry.value = value;
- return result;
- }
-
- /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
- /// If insertion happens, asserts there is enough capacity without allocating.
- pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry {
- const gop = self.getOrPutAssumeCapacity(key);
- var result: ?Entry = null;
- if (gop.found_existing) {
- result = gop.entry.*;
- }
- gop.entry.value = value;
- return result;
- }
-
- pub fn getEntryWithHash(self: Self, key: K, hash: u64) ?*Entry {
- if (self.size == 0) {
- return null;
- }
-
- return @call(.{ .modifier = .always_inline }, _getEntryWithHash, .{ self, key, hash });
- }
-
- fn _getEntryWithHash(self: Self, key: K, hash: u64) ?*Entry {
- const mask = self.capacity() - 1;
- const fingerprint = Metadata.takeFingerprint(hash);
- var idx = @truncate(usize, hash & mask);
-
- var metadata = self.metadata.? + idx;
- while (metadata[0].isUsed() or metadata[0].isTombstone()) {
- if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
- const entry = &self.entries()[idx];
- if (eqlFn(entry.key, key)) {
- return entry;
- }
- }
- idx = (idx + 1) & mask;
- metadata = self.metadata.? + idx;
- }
-
- return null;
- }
-
- pub fn getEntry(self: Self, key: K) ?*Entry {
- if (self.size == 0) {
- return null;
- }
-
- return @call(.{ .modifier = .always_inline }, _getEntryWithHash, .{ self, key, hashFn(key) });
- }
-
- /// Insert an entry if the associated key is not already present, otherwise update preexisting value.
- pub fn put(self: *Self, allocator: Allocator, key: K, value: V) !void {
- const result = try self.getOrPut(allocator, key);
- result.entry.value = value;
- }
-
- /// Insert an entry if the associated key is not already present, otherwise update preexisting value.
- pub fn putWithHash(self: *Self, allocator: Allocator, key: K, hash: u64, value: V) !void {
- const result = try self.getOrPutWithHash(allocator, key, hash);
- result.entry.value = value;
- }
-
- /// Get an optional pointer to the value associated with key, if present.
- pub fn getHash(key: K) u64 {
- return hashFn(key);
- }
-
- /// Get an optional pointer to the value associated with key, if present.
- pub fn getWithHash(self: Self, key: K, hash: u64) ?V {
- if (self.size == 0) {
- return null;
- }
-
- const mask = self.capacity() - 1;
- const fingerprint = Metadata.takeFingerprint(hash);
- var idx = @truncate(usize, hash & mask);
-
- var metadata = self.metadata.? + idx;
- while (metadata[0].isUsed() or metadata[0].isTombstone()) {
- if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
- const entry = &self.entries()[idx];
- if (eqlFn(entry.key, key)) {
- return entry.value;
- }
- }
- idx = (idx + 1) & mask;
- metadata = self.metadata.? + idx;
- }
-
- return null;
- }
-
- /// Get an optional pointer to the value associated with key, if present.
- pub fn get(self: Self, key: K) ?V {
- if (self.size == 0) {
- return null;
- }
-
- const hash = hashFn(key);
- const mask = self.capacity() - 1;
- const fingerprint = Metadata.takeFingerprint(hash);
- var idx = @truncate(usize, hash & mask);
-
- var metadata = self.metadata.? + idx;
- while (metadata[0].isUsed() or metadata[0].isTombstone()) {
- if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
- const entry = &self.entries()[idx];
- if (eqlFn(entry.key, key)) {
- return entry.value;
- }
- }
- idx = (idx + 1) & mask;
- metadata = self.metadata.? + idx;
- }
-
- return null;
- }
-
- pub fn getOrPut(self: *Self, allocator: Allocator, key: K) !GetOrPutResult {
- try self.growIfNeeded(allocator, 1);
-
- return self.getOrPutAssumeCapacity(key);
- }
-
- pub fn getOrPutWithHash(self: *Self, allocator: Allocator, key: K, hash: u64) !GetOrPutResult {
- try self.growIfNeeded(allocator, 1);
-
- return self.getOrPutAssumeCapacityWithHash(key, hash);
- }
-
- pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
- return @call(.{ .modifier = .always_inline }, Self.getOrPutAssumeCapacityWithHash, .{ self, key, hashFn(key) });
- }
-
- pub fn getOrPutAssumeCapacityWithHash(self: *Self, key: K, hash: u64) GetOrPutResult {
- const mask = self.capacity() - 1;
- const fingerprint = Metadata.takeFingerprint(hash);
- var idx = @truncate(usize, hash & mask);
-
- var first_tombstone_idx: usize = self.capacity(); // invalid index
- var metadata = self.metadata.? + idx;
- while (metadata[0].isUsed() or metadata[0].isTombstone()) {
- if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
- const entry = &self.entries()[idx];
- if (eqlFn(entry.key, key)) {
- return GetOrPutResult{ .entry = entry, .found_existing = true };
- }
- } else if (first_tombstone_idx == self.capacity() and metadata[0].isTombstone()) {
- first_tombstone_idx = idx;
- }
-
- idx = (idx + 1) & mask;
- metadata = self.metadata.? + idx;
- }
-
- if (first_tombstone_idx < self.capacity()) {
- // Cheap try to lower probing lengths after deletions. Recycle a tombstone.
- idx = first_tombstone_idx;
- metadata = self.metadata.? + idx;
- } else {
- // We're using a slot previously free.
- self.available -= 1;
- }
-
- metadata[0].fill(fingerprint);
- const entry = &self.entries()[idx];
- entry.* = .{ .key = key, .value = undefined };
- self.size += 1;
-
- return GetOrPutResult{ .entry = entry, .found_existing = false };
- }
-
- pub fn getOrPutValue(self: *Self, allocator: Allocator, key: K, value: V) !*Entry {
- const res = try self.getOrPut(allocator, key);
- if (!res.found_existing) res.entry.value = value;
- return res.entry;
- }
-
- /// Return true if there is a value associated with key in the map.
- pub fn contains(self: *const Self, key: K) bool {
- return self.get(key) != null;
- }
-
- /// If there is an `Entry` with a matching key, it is deleted from
- /// the hash map, and then returned from this function.
- pub fn remove(self: *Self, key: K) ?Entry {
- if (self.size == 0) return null;
-
- const hash = hashFn(key);
- const mask = self.capacity() - 1;
- const fingerprint = Metadata.takeFingerprint(hash);
- var idx = @truncate(usize, hash & mask);
-
- var metadata = self.metadata.? + idx;
- while (metadata[0].isUsed() or metadata[0].isTombstone()) {
- if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
- const entry = &self.entries()[idx];
- if (eqlFn(entry.key, key)) {
- const removed_entry = entry.*;
- metadata[0].remove();
- entry.* = undefined;
- self.size -= 1;
- return removed_entry;
- }
- }
- idx = (idx + 1) & mask;
- metadata = self.metadata.? + idx;
- }
-
- return null;
- }
-
- /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
- /// and discards it.
- pub fn removeAssertDiscard(self: *Self, key: K) void {
- if (@import("builtin").mode != .ReleaseFast) assert(self.contains(key));
-
- const hash = hashFn(key);
- const mask = self.capacity() - 1;
- const fingerprint = Metadata.takeFingerprint(hash);
- var idx = @truncate(usize, hash & mask);
-
- var metadata = self.metadata.? + idx;
- while (metadata[0].isUsed() or metadata[0].isTombstone()) {
- if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
- const entry = &self.entries()[idx];
- if (eqlFn(entry.key, key)) {
- metadata[0].remove();
- entry.* = undefined;
- self.size -= 1;
- return;
- }
- }
- idx = (idx + 1) & mask;
- metadata = self.metadata.? + idx;
- }
-
- unreachable;
- }
-
- fn initMetadatas(self: *Self) void {
- @memset(@ptrCast([*]u8, self.metadata.?), 0, @sizeOf(Metadata) * self.capacity());
- }
-
- // This counts the number of occupied slots, used + tombstones, which is
- // what has to stay under the max_load_percentage of capacity.
- fn load(self: *const Self) Size {
- const max_load = (self.capacity() * max_load_percentage) / 100;
- if (@import("builtin").mode != .ReleaseFast) assert(max_load >= self.available);
- return @truncate(Size, max_load - self.available);
- }
-
- fn growIfNeeded(self: *Self, allocator: Allocator, new_count: Size) !void {
- if (new_count > self.available) {
- try self.grow(allocator, capacityForSize(self.load() + new_count));
- }
- }
-
- pub fn clone(self: Self, allocator: Allocator) !Self {
- var other = Self{};
- if (self.size == 0)
- return other;
-
- const new_cap = capacityForSize(self.size);
- try other.allocate(allocator, new_cap);
- other.initMetadatas();
- other.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
-
- var i: Size = 0;
- var metadata = self.metadata.?;
- var entr = self.entries();
- while (i < self.capacity()) : (i += 1) {
- if (metadata[i].isUsed()) {
- const entry = &entr[i];
- other.putAssumeCapacityNoClobber(entry.key, entry.value);
- if (other.size == self.size)
- break;
- }
- }
-
- return other;
- }
-
- fn grow(self: *Self, allocator: Allocator, new_capacity: Size) !void {
- const new_cap = std.math.max(new_capacity, minimal_capacity);
- if (@import("builtin").mode != .ReleaseFast) assert(new_cap > self.capacity());
- if (@import("builtin").mode != .ReleaseFast) assert(std.math.isPowerOfTwo(new_cap));
-
- var map = Self{};
- defer map.deinit(allocator);
- try map.allocate(allocator, new_cap);
- map.initMetadatas();
- map.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
-
- if (self.size != 0) {
- const old_capacity = self.capacity();
- var i: Size = 0;
- var metadata = self.metadata.?;
- var entr = self.entries();
- while (i < old_capacity) : (i += 1) {
- if (metadata[i].isUsed()) {
- const entry = &entr[i];
- map.putAssumeCapacityNoClobber(entry.key, entry.value);
- if (map.size == self.size)
- break;
- }
- }
- }
-
- self.size = 0;
- std.mem.swap(Self, self, &map);
- }
-
- fn allocate(self: *Self, allocator: Allocator, new_capacity: Size) !void {
- const meta_size = @sizeOf(Header) + new_capacity * @sizeOf(Metadata);
-
- const alignment = @alignOf(Entry) - 1;
- const entries_size = @as(usize, new_capacity) * @sizeOf(Entry) + alignment;
-
- const total_size = meta_size + entries_size;
-
- const slice = try allocator.alignedAlloc(u8, @alignOf(Header), total_size);
- const ptr = @ptrToInt(slice.ptr);
-
- const metadata = ptr + @sizeOf(Header);
- var entry_ptr = ptr + meta_size;
- entry_ptr = (entry_ptr + alignment) & ~@as(usize, alignment);
- if (@import("builtin").mode != .ReleaseFast) assert(entry_ptr + @as(usize, new_capacity) * @sizeOf(Entry) <= ptr + total_size);
-
- const hdr = @intToPtr(*Header, ptr);
- hdr.entries = @intToPtr([*]Entry, entry_ptr);
- hdr.capacity = new_capacity;
- self.metadata = @intToPtr([*]Metadata, metadata);
- }
- };
-}
-
-const testing = std.testing;
-const expect = std.testing.expect;
-const expectEqual = std.testing.expectEqual;
-
-test "std.hash_map basic usage" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- const count = 5;
- var i: u32 = 0;
- var total: u32 = 0;
- while (i < count) : (i += 1) {
- try map.put(i, i);
- total += i;
- }
-
- var sum: u32 = 0;
- var it = map.iterator();
- while (it.next()) |kv| {
- sum += kv.key;
- }
- try expect(sum == total);
-
- i = 0;
- sum = 0;
- while (i < count) : (i += 1) {
- try expectEqual(map.get(i).?, i);
- sum += map.get(i).?;
- }
- try expectEqual(total, sum);
-}
-
-test "std.hash_map ensureCapacity" {
- var map = AutoHashMap(i32, i32).init(std.testing.allocator);
- defer map.deinit();
-
- try map.ensureCapacity(20);
- const initial_capacity = map.capacity();
- try testing.expect(initial_capacity >= 20);
- var i: i32 = 0;
- while (i < 20) : (i += 1) {
- try testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null);
- }
- // shouldn't resize from putAssumeCapacity
- try testing.expect(initial_capacity == map.capacity());
-}
-
-test "std.hash_map ensureCapacity with tombstones" {
- var map = AutoHashMap(i32, i32).init(std.testing.allocator);
- defer map.deinit();
-
- var i: i32 = 0;
- while (i < 100) : (i += 1) {
- try map.ensureCapacity(@intCast(u32, map.count() + 1));
- map.putAssumeCapacity(i, i);
- // Remove to create tombstones that still count as load in the hashmap.
- _ = map.remove(i);
- }
-}
-
-test "std.hash_map clearRetainingCapacity" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- map.clearRetainingCapacity();
-
- try map.put(1, 1);
- try expectEqual(map.get(1).?, 1);
- try expectEqual(map.count(), 1);
-
- map.clearRetainingCapacity();
- map.putAssumeCapacity(1, 1);
- try expectEqual(map.get(1).?, 1);
- try expectEqual(map.count(), 1);
-
- const cap = map.capacity();
- try expect(cap > 0);
-
- map.clearRetainingCapacity();
- map.clearRetainingCapacity();
- try expectEqual(map.count(), 0);
- try expectEqual(map.capacity(), cap);
- try expect(!map.contains(1));
-}
-
-test "std.hash_map grow" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- const growTo = 12456;
-
- var i: u32 = 0;
- while (i < growTo) : (i += 1) {
- try map.put(i, i);
- }
- try expectEqual(map.count(), growTo);
-
- i = 0;
- var it = map.iterator();
- while (it.next()) |kv| {
- try expectEqual(kv.key, kv.value);
- i += 1;
- }
- try expectEqual(i, growTo);
-
- i = 0;
- while (i < growTo) : (i += 1) {
- try expectEqual(map.get(i).?, i);
- }
-}
-
-test "std.hash_map clone" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- var a = try map.clone();
- defer a.deinit();
-
- try expectEqual(a.count(), 0);
-
- try a.put(1, 1);
- try a.put(2, 2);
- try a.put(3, 3);
-
- var b = try a.clone();
- defer b.deinit();
-
- try expectEqual(b.count(), 3);
- try expectEqual(b.get(1), 1);
- try expectEqual(b.get(2), 2);
- try expectEqual(b.get(3), 3);
-}
-
-test "std.hash_map ensureCapacity with existing elements" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- try map.put(0, 0);
- try expectEqual(map.count(), 1);
- try expectEqual(map.capacity(), @TypeOf(map).Unmanaged.minimal_capacity);
-
- try map.ensureCapacity(65);
- try expectEqual(map.count(), 1);
- try expectEqual(map.capacity(), 128);
-}
-
-test "std.hash_map ensureCapacity satisfies max load factor" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- try map.ensureCapacity(127);
- try expectEqual(map.capacity(), 256);
-}
-
-test "std.hash_map remove" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- var i: u32 = 0;
- while (i < 16) : (i += 1) {
- try map.put(i, i);
- }
-
- i = 0;
- while (i < 16) : (i += 1) {
- if (i % 3 == 0) {
- _ = map.remove(i);
- }
- }
- try expectEqual(map.count(), 10);
- var it = map.iterator();
- while (it.next()) |kv| {
- try expectEqual(kv.key, kv.value);
- try expect(kv.key % 3 != 0);
- }
-
- i = 0;
- while (i < 16) : (i += 1) {
- if (i % 3 == 0) {
- try expect(!map.contains(i));
- } else {
- try expectEqual(map.get(i).?, i);
- }
- }
-}
-
-test "std.hash_map reverse removes" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- var i: u32 = 0;
- while (i < 16) : (i += 1) {
- try map.putNoClobber(i, i);
- }
-
- i = 16;
- while (i > 0) : (i -= 1) {
- _ = map.remove(i - 1);
- try expect(!map.contains(i - 1));
- var j: u32 = 0;
- while (j < i - 1) : (j += 1) {
- try expectEqual(map.get(j).?, j);
- }
- }
-
- try expectEqual(map.count(), 0);
-}
-
-test "std.hash_map multiple removes on same metadata" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- var i: u32 = 0;
- while (i < 16) : (i += 1) {
- try map.put(i, i);
- }
-
- _ = map.remove(7);
- _ = map.remove(15);
- _ = map.remove(14);
- _ = map.remove(13);
- try expect(!map.contains(7));
- try expect(!map.contains(15));
- try expect(!map.contains(14));
- try expect(!map.contains(13));
-
- i = 0;
- while (i < 13) : (i += 1) {
- if (i == 7) {
- try expect(!map.contains(i));
- } else {
- try expectEqual(map.get(i).?, i);
- }
- }
-
- try map.put(15, 15);
- try map.put(13, 13);
- try map.put(14, 14);
- try map.put(7, 7);
- i = 0;
- while (i < 16) : (i += 1) {
- try expectEqual(map.get(i).?, i);
- }
-}
-
-test "std.hash_map put and remove loop in random order" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- var keys = std.ArrayList(u32).init(std.testing.allocator);
- defer keys.deinit();
-
- const size = 32;
- const iterations = 100;
-
- var i: u32 = 0;
- while (i < size) : (i += 1) {
- try keys.append(i);
- }
- var rng = std.rand.DefaultPrng.init(0);
-
- while (i < iterations) : (i += 1) {
- std.rand.Random.shuffle(rng.random(), u32, keys.items);
-
- for (keys.items) |key| {
- try map.put(key, key);
- }
- try expectEqual(map.count(), size);
-
- for (keys.items) |key| {
- _ = map.remove(key);
- }
- try expectEqual(map.count(), 0);
- }
-}
-
-test "std.hash_map remove one million elements in random order" {
- const Map = AutoHashMap(u32, u32);
- const n = 1000 * 1000;
- var map = Map.init(std.heap.page_allocator);
- defer map.deinit();
-
- var keys = std.ArrayList(u32).init(std.heap.page_allocator);
- defer keys.deinit();
-
- var i: u32 = 0;
- while (i < n) : (i += 1) {
- keys.append(i) catch unreachable;
- }
-
- var rng = std.rand.DefaultPrng.init(0);
- std.rand.Random.shuffle(rng.random(), u32, keys.items);
-
- for (keys.items) |key| {
- map.put(key, key) catch unreachable;
- }
-
- std.rand.Random.shuffle(rng.random(), u32, keys.items);
- i = 0;
- while (i < n) : (i += 1) {
- const key = keys.items[i];
- _ = map.remove(key);
- }
-}
-
-test "std.hash_map put" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- var i: u32 = 0;
- while (i < 16) : (i += 1) {
- try map.put(i, i);
- }
-
- i = 0;
- while (i < 16) : (i += 1) {
- try expectEqual(map.get(i).?, i);
- }
-
- i = 0;
- while (i < 16) : (i += 1) {
- try map.put(i, i * 16 + 1);
- }
-
- i = 0;
- while (i < 16) : (i += 1) {
- try expectEqual(map.get(i).?, i * 16 + 1);
- }
-}
-
-test "std.hash_map putAssumeCapacity" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- try map.ensureCapacity(20);
- var i: u32 = 0;
- while (i < 20) : (i += 1) {
- map.putAssumeCapacityNoClobber(i, i);
- }
-
- i = 0;
- var sum = i;
- while (i < 20) : (i += 1) {
- sum += map.get(i).?;
- }
- try expectEqual(sum, 190);
-
- i = 0;
- while (i < 20) : (i += 1) {
- map.putAssumeCapacity(i, 1);
- }
-
- i = 0;
- sum = i;
- while (i < 20) : (i += 1) {
- sum += map.get(i).?;
- }
- try expectEqual(sum, 20);
-}
-
-test "std.hash_map getOrPut" {
- var map = AutoHashMap(u32, u32).init(std.testing.allocator);
- defer map.deinit();
-
- var i: u32 = 0;
- while (i < 10) : (i += 1) {
- try map.put(i * 2, 2);
- }
-
- i = 0;
- while (i < 20) : (i += 1) {
- _ = try map.getOrPutValue(i, 1);
- }
-
- i = 0;
- var sum = i;
- while (i < 20) : (i += 1) {
- sum += map.get(i).?;
- }
-
- try expectEqual(sum, 30);
-}
-
-test "std.hash_map basic hash map usage" {
- var map = AutoHashMap(i32, i32).init(std.testing.allocator);
- defer map.deinit();
-
- try testing.expect((try map.fetchPut(1, 11)) == null);
- try testing.expect((try map.fetchPut(2, 22)) == null);
- try testing.expect((try map.fetchPut(3, 33)) == null);
- try testing.expect((try map.fetchPut(4, 44)) == null);
-
- try map.putNoClobber(5, 55);
- try testing.expect((try map.fetchPut(5, 66)).?.value == 55);
- try testing.expect((try map.fetchPut(5, 55)).?.value == 66);
-
- const gop1 = try map.getOrPut(5);
- try testing.expect(gop1.found_existing == true);
- try testing.expect(gop1.entry.value == 55);
- gop1.entry.value = 77;
- try testing.expect(map.getEntry(5).?.value == 77);
-
- const gop2 = try map.getOrPut(99);
- try testing.expect(gop2.found_existing == false);
- gop2.entry.value = 42;
- try testing.expect(map.getEntry(99).?.value == 42);
-
- const gop3 = try map.getOrPutValue(5, 5);
- try testing.expect(gop3.value == 77);
-
- const gop4 = try map.getOrPutValue(100, 41);
- try testing.expect(gop4.value == 41);
-
- try testing.expect(map.contains(2));
- try testing.expect(map.getEntry(2).?.value == 22);
- try testing.expect(map.get(2).? == 22);
-
- const rmv1 = map.remove(2);
- try testing.expect(rmv1.?.key == 2);
- try testing.expect(rmv1.?.value == 22);
- try testing.expect(map.remove(2) == null);
- try testing.expect(map.getEntry(2) == null);
- try testing.expect(map.get(2) == null);
-
- map.removeAssertDiscard(3);
-}
-
-test "std.hash_map clone" {
- var original = AutoHashMap(i32, i32).init(std.testing.allocator);
- defer original.deinit();
-
- var i: u8 = 0;
- while (i < 10) : (i += 1) {
- try original.putNoClobber(i, i * 10);
- }
-
- var copy = try original.clone();
- defer copy.deinit();
-
- i = 0;
- while (i < 10) : (i += 1) {
- try testing.expect(copy.get(i).? == i * 10);
- }
-}
diff --git a/src/js_ast.zig b/src/js_ast.zig
index e206e590b..ffdce6783 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -20,11 +20,7 @@ const allocators = @import("allocators.zig");
const JSC = @import("bun").JSC;
const HTTP = @import("bun").HTTP;
const RefCtx = @import("./ast/base.zig").RefCtx;
-const _hash_map = @import("hash_map.zig");
const JSONParser = @import("./json_parser.zig");
-const StringHashMap = _hash_map.StringHashMap;
-const AutoHashMap = _hash_map.AutoHashMap;
-const StringHashMapUnmanaged = _hash_map.StringHashMapUnmanaged;
const is_bindgen = std.meta.globalOption("bindgen", bool) orelse false;
const ComptimeStringMap = bun.ComptimeStringMap;
const JSPrinter = @import("./js_printer.zig");
@@ -4664,12 +4660,13 @@ pub const StrictModeKind = enum(u7) {
};
pub const Scope = struct {
+ pub const MemberHashMap = bun.StringHashMapUnmanaged(Member);
+
id: usize = 0,
kind: Kind = Kind.block,
parent: ?*Scope,
children: std.ArrayListUnmanaged(*Scope) = .{},
- // This is a special hash table that allows us to pass in the hash directly
- members: StringHashMapUnmanaged(Member) = .{},
+ members: MemberHashMap = .{},
generated: std.ArrayListUnmanaged(Ref) = .{},
// This is used to store the ref of the label symbol for ScopeLabel scopes.
@@ -4688,6 +4685,31 @@ pub const Scope = struct {
is_after_const_local_prefix: bool = false,
+ pub fn getMemberHash(name: []const u8) u64 {
+ return bun.StringHashMapContext.hash(.{}, name);
+ }
+
+ pub fn getMemberWithHash(this: *const Scope, name: []const u8, hash_value: u64) ?Member {
+ const hashed = bun.StringHashMapContext.Prehashed{
+ .value = hash_value,
+ .input = name,
+ };
+ return this.members.getAdapted(name, hashed);
+ }
+
+ pub fn getOrPutMemberWithHash(
+ this: *Scope,
+ allocator: std.mem.Allocator,
+ name: []const u8,
+ hash_value: u64,
+ ) !MemberHashMap.GetOrPutResult {
+ const hashed = bun.StringHashMapContext.Prehashed{
+ .value = hash_value,
+ .input = name,
+ };
+ return this.members.getOrPutContextAdapted(allocator, name, hashed, .{});
+ }
+
pub fn reset(this: *Scope) void {
this.children.clearRetainingCapacity();
this.generated.clearRetainingCapacity();
@@ -4829,7 +4851,7 @@ pub const Scope = struct {
}
}
- pub fn kindStopsHoisting(s: *Scope) bool {
+ pub inline fn kindStopsHoisting(s: *const Scope) bool {
return @enumToInt(s.kind) >= @enumToInt(Kind.entry);
}
};
diff --git a/src/js_parser.zig b/src/js_parser.zig
index e63b7f754..deb6d09d7 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -11,7 +11,6 @@ pub const RuntimeImports = _runtime.Runtime.Imports;
pub const RuntimeFeatures = _runtime.Runtime.Features;
pub const RuntimeNames = _runtime.Runtime.Names;
pub const fs = @import("./fs.zig");
-const _hash_map = @import("./hash_map.zig");
const bun = @import("bun");
const string = bun.string;
const Output = bun.Output;
@@ -67,9 +66,9 @@ pub const locModuleScope = logger.Loc{ .start = -100 };
const Ref = @import("./ast/base.zig").Ref;
const RefHashCtx = @import("./ast/base.zig").RefHashCtx;
-pub const StringHashMap = _hash_map.StringHashMap;
-pub const AutoHashMap = _hash_map.AutoHashMap;
-const StringHashMapUnamanged = _hash_map.StringHashMapUnamanged;
+pub const StringHashMap = bun.StringHashMap;
+pub const AutoHashMap = bun.AutoHashMap;
+const StringHashMapUnamanged = bun.StringHashMapUnmanaged;
const ObjectPool = @import("./pool.zig").ObjectPool;
const NodeFallbackModules = @import("./node_fallbacks.zig");
@@ -1940,7 +1939,7 @@ const StrictModeFeature = enum {
if_else_function_stmt,
};
-const Map = _hash_map.AutoHashMapUnmanaged;
+const Map = std.AutoHashMapUnmanaged;
const List = std.ArrayListUnmanaged;
const ListManaged = std.ArrayList;
@@ -3617,7 +3616,7 @@ fn NewParser_(
should_fold_numeric_constants: bool = false,
emitted_namespace_vars: RefMap = RefMap{},
is_exported_inside_namespace: RefRefMap = .{},
- known_enum_values: Map(Ref, _hash_map.StringHashMapUnmanaged(f64)) = .{},
+ known_enum_values: Map(Ref, StringHashMapUnamanged(f64)) = .{},
local_type_names: StringBoolMap = StringBoolMap{},
// This is the reference to the generated function argument for the namespace,
@@ -4255,7 +4254,7 @@ fn NewParser_(
// This function can show up in profiling.
// That's part of why we do this.
// Instead of rehashing `name` for every scope, we do it just once.
- const hash = @TypeOf(p.module_scope.members).getHash(name);
+ const hash = Scope.getMemberHash(name);
const allocator = p.allocator;
const ref: Ref = brk: {
@@ -4278,7 +4277,7 @@ fn NewParser_(
}
// Is the symbol a member of this scope?
- if (scope.members.getWithHash(name, hash)) |member| {
+ if (scope.getMemberWithHash(name, hash)) |member| {
declare_loc = member.loc;
break :brk member.ref;
}
@@ -4288,7 +4287,7 @@ fn NewParser_(
p.checkForNonBMPCodePoint(loc, name);
const _ref = p.newSymbol(.unbound, name) catch unreachable;
declare_loc = loc;
- p.module_scope.members.putWithHash(allocator, name, hash, js_ast.Scope.Member{ .ref = _ref, .loc = logger.Loc.Empty }) catch unreachable;
+ p.module_scope.members.putNoClobber(allocator, name, js_ast.Scope.Member{ .ref = _ref, .loc = logger.Loc.Empty }) catch unreachable;
break :brk _ref;
};
@@ -5080,7 +5079,7 @@ fn NewParser_(
}
try p.module_scope.generated.ensureUnusedCapacity(p.allocator, generated_symbols_count * 3);
- try p.module_scope.members.ensureCapacity(p.allocator, generated_symbols_count * 3 + p.module_scope.members.count());
+ try p.module_scope.members.ensureUnusedCapacity(p.allocator, generated_symbols_count * 3 + p.module_scope.members.count());
p.exports_ref = try p.declareCommonJSSymbol(.hoisted, "exports");
p.module_ref = try p.declareCommonJSSymbol(.hoisted, "module");
@@ -5242,7 +5241,8 @@ fn NewParser_(
var iter = scope.members.iterator();
const allocator = p.allocator;
nextMember: while (iter.next()) |res| {
- var symbol = &p.symbols.items[res.value.ref.innerIndex()];
+ const value = res.value_ptr.*;
+ var symbol = &p.symbols.items[value.ref.innerIndex()];
if (!symbol.isHoisted()) {
continue :nextMember;
}
@@ -5250,10 +5250,7 @@ fn NewParser_(
// Check for collisions that would prevent to hoisting "var" symbols up to the enclosing function scope
var __scope = scope.parent;
- var hash: u64 = undefined;
- if (__scope) |_scope| {
- hash = @TypeOf(_scope.members).getHash(symbol.original_name);
- }
+ const hash: u64 = Scope.getMemberHash(symbol.original_name);
while (__scope) |_scope| {
// Variable declarations hoisted past a "with" statement may actually end
@@ -5270,9 +5267,25 @@ fn NewParser_(
symbol.must_not_be_renamed = true;
}
- if (_scope.members.getEntryWithHash(symbol.original_name, hash)) |existing_member_entry| {
- const existing_member = &existing_member_entry.value;
- const existing_symbol: *const Symbol = &p.symbols.items[existing_member.ref.innerIndex()];
+ var member_ptr: *Scope.Member = undefined;
+ const member_in_scope_: ?Scope.Member = brk: {
+ if (_scope.kindStopsHoisting()) {
+ var entry = _scope.getOrPutMemberWithHash(allocator, symbol.original_name, hash) catch unreachable;
+ member_ptr = entry.value_ptr;
+
+ if (!entry.found_existing) {
+ entry.key_ptr.* = symbol.original_name;
+ break :brk null;
+ } else {
+ break :brk member_ptr.*;
+ }
+ } else {
+ break :brk _scope.getMemberWithHash(symbol.original_name, hash);
+ }
+ };
+
+ if (member_in_scope_) |member_in_scope| {
+ const existing_symbol: *const Symbol = &p.symbols.items[member_in_scope.ref.innerIndex()];
// We can hoist the symbol from the child scope into the symbol in
// this scope if:
@@ -5286,7 +5299,7 @@ fn NewParser_(
(Symbol.isKindFunction(existing_symbol.kind) and (_scope.kind == .entry or _scope.kind == .function_body)))
{
// Silently merge this symbol into the existing symbol
- symbol.link = existing_member.ref;
+ symbol.link = member_in_scope.ref;
continue :nextMember;
}
@@ -5296,7 +5309,7 @@ fn NewParser_(
// An identifier binding from a catch statement and a function
// declaration can both silently shadow another hoisted symbol
if (symbol.kind != .catch_identifier and symbol.kind != .hoisted_function) {
- const r = js_lexer.rangeOfIdentifier(p.source, res.value.loc);
+ const r = js_lexer.rangeOfIdentifier(p.source, value.loc);
var notes = allocator.alloc(logger.Data, 1) catch unreachable;
notes[0] =
logger.rangeData(
@@ -5309,7 +5322,7 @@ fn NewParser_(
) catch unreachable,
);
- p.log.addRangeErrorFmtWithNotes(p.source, js_lexer.rangeOfIdentifier(p.source, existing_member_entry.value.loc), allocator, notes, "{s} has already been declared", .{symbol.original_name}) catch unreachable;
+ p.log.addRangeErrorFmtWithNotes(p.source, js_lexer.rangeOfIdentifier(p.source, member_in_scope.loc), allocator, notes, "{s} has already been declared", .{symbol.original_name}) catch unreachable;
}
continue :nextMember;
@@ -5317,9 +5330,9 @@ fn NewParser_(
}
if (_scope.kindStopsHoisting()) {
- _scope.members.putWithHash(allocator, symbol.original_name, hash, res.value) catch unreachable;
- break;
+ member_ptr.* = value;
}
+
__scope = _scope.parent;
}
}
@@ -5398,11 +5411,12 @@ fn NewParser_(
var iter = scope.parent.?.members.iterator();
while (iter.next()) |entry| {
- // // Don't copy down the optional function expression name. Re-declaring
- // // the name of a function expression is allowed.
- const adjacent_kind = p.symbols.items[entry.value.ref.innerIndex()].kind;
+ // Don't copy down the optional function expression name. Re-declaring
+ // the name of a function expression is allowed.
+ const value = entry.value_ptr.*;
+ const adjacent_kind = p.symbols.items[value.ref.innerIndex()].kind;
if (adjacent_kind != .hoisted_function) {
- try scope.members.put(allocator, entry.key, entry.value);
+ try scope.members.put(allocator, entry.key_ptr.*, value);
}
}
}
@@ -9384,8 +9398,8 @@ fn NewParser_(
}
pub fn declareCommonJSSymbol(p: *P, comptime kind: Symbol.Kind, comptime name: string) !Ref {
- const name_hash = comptime @TypeOf(p.module_scope.members).getHash(name);
- const member = p.module_scope.members.getWithHash(name, name_hash);
+ const name_hash = comptime Scope.getMemberHash(name);
+ const member = p.module_scope.getMemberWithHash(name, name_hash);
// If the code declared this symbol using "var name", then this is actually
// not a collision. For example, node will let you do this:
@@ -9415,7 +9429,7 @@ fn NewParser_(
const ref = try p.newSymbol(kind, name);
if (member == null) {
- try p.module_scope.members.putWithHash(p.allocator, name, name_hash, Scope.Member{ .ref = ref, .loc = logger.Loc.Empty });
+ try p.module_scope.members.put(p.allocator, name, Scope.Member{ .ref = ref, .loc = logger.Loc.Empty });
return ref;
}
@@ -9457,7 +9471,7 @@ fn NewParser_(
const scope = p.current_scope;
var entry = try scope.members.getOrPut(p.allocator, name);
if (entry.found_existing) {
- const existing = entry.entry.value;
+ const existing = entry.value_ptr.*;
var symbol: *Symbol = &p.symbols.items[existing.ref.innerIndex()];
if (comptime !is_generated) {
@@ -9513,8 +9527,8 @@ fn NewParser_(
p.symbols.items[ref.innerIndex()].link = existing.ref;
}
}
-
- entry.entry.value = js_ast.Scope.Member{ .ref = ref, .loc = loc };
+ entry.key_ptr.* = name;
+ entry.value_ptr.* = js_ast.Scope.Member{ .ref = ref, .loc = loc };
if (comptime is_generated) {
try p.module_scope.generated.append(p.allocator, ref);
}
@@ -9992,7 +10006,7 @@ fn NewParser_(
// continue;
// }
- p.symbols.items[member.value.ref.innerIndex()].must_not_be_renamed = true;
+ p.symbols.items[member.value_ptr.ref.innerIndex()].must_not_be_renamed = true;
}
}
@@ -16308,7 +16322,7 @@ fn NewParser_(
// Track values so they can be used by constant folding. We need to follow
// links here in case the enum was merged with a preceding namespace
- var values_so_far = _hash_map.StringHashMapUnmanaged(f64){};
+ var values_so_far = StringHashMapUnamanged(f64){};
p.known_enum_values.put(allocator, data.name.ref orelse p.panic("Expected data.name.ref", .{}), values_so_far) catch unreachable;
p.known_enum_values.put(allocator, data.arg, values_so_far) catch unreachable;