diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index 5e018e4a3..33ab1c436 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -46,4 +46,4 @@ Public API: the translator's output for moq-net's model. ## Required -- [hashbrown as the runtime's map](/quest/m2/ts-model/hashbrown.md) - hashlink's `hashbrown` tables translate as `rt.HashMap`, with std's capacity +- [A crate's own hasher](/quest/m2/ts-model/own-hasher.md) - hashlink's default hasher, a crate's own `Hasher`, hashes keys as native's does diff --git a/quest/m2/ts-model/hashbrown.md b/quest/m2/ts-model/hashbrown.md deleted file mode 100644 index 8aae1c57b..000000000 --- a/quest/m2/ts-model/hashbrown.md +++ /dev/null @@ -1,24 +0,0 @@ -# [M] hashbrown as the runtime's map - -## Goal - -rust2-ts translates `hashbrown`'s `HashMap`, `HashSet`, and `HashTable`, which hashlink -keeps its entries in, as it translates std's: an `rt.HashMap` and its runtime functions, -proven by differential tests against native Rust. - -## Plan - -Found by [#201](https://github.com/moq-dev/rust2/pull/201) on 2026-10-28: with hashlink's -raw pointers translated, hashbrown is the model's largest source of refusals left -(251 lines of its census), its SIMD groups, raw allocation, and byte reinterpretation -being what JS has no counterpart of. - -- Map it as std's map is: std's `HashMap` is hashbrown's, so the same runtime functions - stand in for both, with no config. -- `capacity` follows hashbrown's `capacity_to_buckets` and `allocation_size` exactly. - Open: a `Vec`'s follows only std's guarantees and doubles as it grows (decided - 2026-11-01), so whether a map's must be exact is the maintainer's call. -- `HashTable`'s methods take the hash and the equality from the caller, so it needs a - table keyed by the caller's hash, not the runtime's own. - -Public API: the translator's output. diff --git a/quest/m2/ts-model/own-hasher.md b/quest/m2/ts-model/own-hasher.md new file mode 100644 index 000000000..e0949307c --- /dev/null +++ b/quest/m2/ts-model/own-hasher.md @@ -0,0 +1,25 @@ +# [M] A crate's own hasher + +## Goal + +[rust2-ts](/ts/README.md) translates hashing through a crate's own `Hasher` and +`BuildHasher`, as `hashlink`'s default `DefaultHashBuilder` wraps hashbrown's: std's +provided `BuildHasher::hash_one`, and std's `Hash` impls writing into that hasher with the +calls std makes, proven by differential tests against native Rust. + +## Plan + +Found by [#398](https://github.com/moq-dev/rust2/pull/398), which maps hashbrown's +`DefaultHashBuilder` and `DefaultHasher` to std's: a `LinkedHashMap::new()` then refuses +with "unsupported call to `::hash_one::<&u16>`". moq's model reaches it through hashlink's +default hasher, the census's last refusals in `hashlink/src/lib.rs`. + +- `hash_one` is `build_hasher`, then `Hash::hash`, then `finish`, each the crate's. +- std's `Hash` of an integer, `str`, slice, tuple, and the rest calls the hasher's + `write_*` methods in std's order, length prefixes and `str`'s `0xff` included, so a + hasher that reads its input, not only hashlink's forwarding one, sees what native's does. +- Today only std's `DefaultHasher` takes std's `Hash` impls, written by the runtime's + functions. + +Public API: the translator's output. diff --git a/quest/m3/README.md b/quest/m3/README.md index d0e05438c..29696d8e2 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -40,4 +40,5 @@ generic std gap is planned here even when moq never calls it. - [Type errors at the Rust that made them](/quest/m3/ts-span-precision.md) - a `tsc` error on output names the tail expression or call argument that made it - [An empty iterator of mutable references](/quest/m3/ts-empty-places.md) - `iter::empty::<&mut T>()` given to `zip` or `chain` translates - [An Option of numbers in the result slot](/quest/m3/ts-option-slot.md) - `varint_opt`'s `Option` comes back through the slot with no object per call +- [Dropping a crate's generic type inside another](/quest/m3/ts-nested-generic-drop.md) - an `LruCache` holding a `LinkedHashMap` drops as rustc's glue does - [Fresh writes through a zip, a copy, and a repeat](/quest/m3/ts-fresh-shapes.md) - zipping a `Vec<[T; N]>`, reading a `T` out of `Zip::next`, and `copied` or `vec![x; n]` over a `Copy` type translate diff --git a/quest/m3/ts-nested-generic-drop.md b/quest/m3/ts-nested-generic-drop.md new file mode 100644 index 000000000..ad3ef459d --- /dev/null +++ b/quest/m3/ts-nested-generic-drop.md @@ -0,0 +1,20 @@ +# [S] Dropping a crate's generic type inside another + +## Goal + +[rust2-ts](/ts/README.md) drops a crate's generic type held in a field of another of the +crate's generic types, as `hashlink`'s `LruCache` holds a `LinkedHashMap`, in the order rustc's glue does, proven by differential tests against native Rust. + +## Plan + +Found by [hashbrown as the runtime's map](https://github.com/moq-dev/rust2/pull/398): a +fixture dropping an `LruCache` refuses with "dropping `hashlink::LinkedHashMap`, +which is not supported yet" at the end of the function holding it, from +`drop_glue_call`, which takes the generic field's glue only in a census. A +`LinkedHashMap` dropped by itself translates, so the outer glue's shim is what lowers the +field over its type parameters rather than the instance. `LinkedHashSet` is expected to +refuse the same way. No consumer calls them yet; they are planned here as part of +covering stock crates. + +Public API: the translator's output. diff --git a/ts/Cargo.lock b/ts/Cargo.lock index 5893e081c..9921d2068 100644 --- a/ts/Cargo.lock +++ b/ts/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "base64-simd" version = "0.8.0" @@ -87,6 +93,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120339709b24832c07464b0838828361b735d793c409b8ca3147db0dae601542" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "getrandom" version = "0.4.3" @@ -104,6 +116,20 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" +dependencies = [ + "hashbrown", +] [[package]] name = "indexmap" @@ -311,6 +337,8 @@ version = "0.0.0" dependencies = [ "bytes", "getrandom", + "hashbrown", + "hashlink", "rand", "tracing", ] diff --git a/ts/README.md b/ts/README.md index 66692bd28..fde6a1bc7 100644 --- a/ts/README.md +++ b/ts/README.md @@ -137,6 +137,8 @@ crate translation reaches is refused, and so are two versions of one crate it re | `bytes::Bytes` | `rt.ByteSlice`, as `&[u8]` is: its clones and slices share bytes that never change | | `bytes::BytesMut` | `rt.BytesMut`: a window of storage, which what it splits off and freezes share without overlapping | | `rand::rngs::ThreadRng` | `rt.ThreadRng`, which holds no state | +| `hashbrown::HashMap`, `HashSet` | `rt.HashMap` and `rt.HashSet`, as std's, which are hashbrown's | +| `hashbrown::HashTable` | `rt.HashTable`: an `rt.HashMap` of nothing to each item, under the hash its caller gives; its entries are `rt.TableEntry`, the place of the item an occupied one holds | `Vec` storage and `[x; N]` byte arrays up to 1 KiB share 8 KiB `ArrayBuffer`s, as Node's `Buffer` pool does, so the `buffer` behind a view holds other bytes too: read it through @@ -527,6 +529,24 @@ of them: The rest of `bytes`, such as the other provided methods of the crate's own `Buf` and the impls forwarding through a `&mut` or a `Box`, translates from its MIR, refused where that is. +- `hashbrown`'s `HashMap` and `HashSet` are std's, which wrap them, and map as std's do, + hashbrown's own methods included: `allocation_size` is a slot and a control byte for each + entry the capacity holds, and its `Entry` is std's. Its `HashTable` keeps each item + under the hash the caller gives, a `u64` past 2^53 included, chained by the caller's + equality, which the runtime calls on each item under that hash, rather than on those + hashbrown's tag bytes pick. It never calls a method's `hasher`, as it never moves an + item. `find`, `find_mut`, `find_entry`, `entry`, `insert_unique`, `retain`, `iter`, + `iter_mut`, `drain`, `clear`, `len`, `clone`, `Debug`, and what reads and sets capacity + map, and so do its entries' `insert`, `or_insert`, `or_insert_with`, `and_modify`, + `get`, `get_mut`, `into_mut`, `remove`, and `into_table`. What reads hashbrown's + buckets, such as `find_bucket_index` and `iter_hash`, is refused, as are + `get_many_mut`, `extract_if`, and `try_reserve`. hashbrown's `DefaultHashBuilder` and + `DefaultHasher` are std's `RandomState` and `DefaultHasher`: foldhash's state is seeded + at random as std's is, so which hash a key gets is never observable. A crate's own + `Hasher`, such as `hashlink`'s default over hashbrown's, is refused + ([a crate's own hasher](/quest/m2/ts-model/own-hasher.md)). Nothing of hashbrown + translates, so `hashlink`, which keeps its entries in a `HashTable`, translates over the + runtime's. - `rand::rng()` is an `rt.ThreadRng`, whose `random` of an integer or `bool` and `random_range` of an integer range map to `rt/rand.ts`. Each value comes from `crypto.getRandomValues`, uniform as `rand`'s are, though no seed reproduces them, as @@ -742,7 +762,13 @@ they need. it takes 0.5-0.8x the time two sorted arrays did, from 16 keys to 4096, in Node, Bun, and Chrome; 64 keys a node measured best of 16 to 128. A hash map's JS primitive keys are a JS `Map`'s, the engine's own hash table; its other keys go into a second `Map` - by their translated hash, chained by `Eq`. `VecDeque` is a ring buffer over an array + by their translated hash, chained by `Eq`. A hash map, set, or table holds only what std + guarantees of its capacity, at least its length and what `with_capacity` and `reserve` + ask for, doubling as inserts grow it, and keeps it as it holds fewer or clears, as a + table keeps its allocation; hashbrown's exact bucket counts are not followed (decided + with the maintainer on 2026-11-01). It holds one, as `cap`, only where an operation + sets it or a removal reads it, so an insert never reads or writes one, and reading it + doubles it from 3 while short of the length. `VecDeque` is a ring buffer over an array whose length is a power of two. A `Vec`, `VecDeque`, and `BinaryHeap` hold what std guarantees of their capacity: at least their length and what `with_capacity`, `reserve`, and `reserve_exact` ask for, and std's exactly where its doubling fixes it, @@ -810,7 +836,7 @@ an iterator adaptor, and values that drop in some iterators ([drop order](/quest function without a mapping of its own that makes or reads an `Option` whose `None` is `rt.None`, as `Iterator::scan` of `Option`s does, an `Option` of an `Option` of `()` or another `Option`, an instance of a generic type whose field is an `Option` of a type parameter -that can be `undefined`, a `String`'s or hash collection's `capacity`, looking up a key by +that can be `undefined`, a `String`'s `capacity`, looking up a key by another type it borrows as through the crate's own `Borrow`, a `&mut` to a value JS copies that std makes, other than a map's, or hands a closure, as `iter_mut` and `and_modify` do, one into a `Result`'s payload passed on or kept, `Hasher::write` of bytes, the adaptors @@ -888,13 +914,17 @@ built natively for wasm32 and run beside its translation on the same inputs by impls, beside values that drop inside std's types. Its caller is hand-written TS (`tests/task.ts`): a FIFO executor that runs a script of spawns, sends, polls, and drops, and logs every poll, wake, and drop, as the fixture's native executor does. The logs must match byte for byte. -- `stock` calls `bytes`, `rand`, and `tracing` with no config, against native Rust, whose +- `stock` calls `bytes`, `hashbrown`, `rand`, and `tracing` with no config, and `hashlink` + over `hashbrown`, against native Rust, whose `rand::rng()` reads the fixture's own getrandom backend on wasm32. It checks `Bytes`'s slices and panics, how each is made and printed, that no event evaluates a field, and that each random value is in its range, every value of a small range drawn. It splits, freezes, grows, and writes a `BytesMut` by ops its input picks, ending with every piece's bytes, and reads and writes through `Buf` and `BufMut` generically, a buffer of - its own included. `caller.ts` names the inputs that reserve at the edges of + its own included. It changes hashbrown's maps and sets, keyed by JS primitives and by + tuples, and `HashTable`s of tuples and of numbers, whose places it writes, under hashes + that collide and that pass 2^53, checks their capacity by std's guarantees, and writes a + `hashlink::LinkedHashMap` in its own order. `caller.ts` names the inputs that reserve at the edges of `BytesMut`'s 512 MiB limit. - `bind` binds `buf::Blob`, a renamed dependency's type with `unsafe` inside, and a function and a type of its own that read through raw pointers, the type dropping an `Arc` where Rust drops it, to the TS in `bind/`. It keys diff --git a/ts/bench/hashed.ts b/ts/bench/hashed.ts new file mode 100644 index 000000000..5a907c308 --- /dev/null +++ b/ts/bench/hashed.ts @@ -0,0 +1,22 @@ +// The `hash/` workloads' hand-written route: `bench.rs`'s op 14 over a `Map`, as TypeScript +// keeps a map of numbers. + +/** Run op 14, as `bench::run` does, for the count `data` starts with: with `selector` 1, inserts alone. */ +export function hashed(selector: number, data: Uint8Array): Uint8Array { + const padded = new Uint8Array(8); + padded.set(data.subarray(0, 8)); + const count = Math.min(Number(new DataView(padded.buffer).getBigUint64(0)), 1 << 16); + const key = (i: number) => (i * 40503) % (1 << 16); + const map = new Map(); + for (let i = 0; i < count; i++) map.set(key(i), i); + let sum = 0; + if (selector !== 1) { + for (let i = 0; i < count; i++) sum += map.get(key(count - 1 - i)) ?? 0; + for (let i = 0; i < count; i++) map.delete(key(i)); + } + const out = new Uint8Array(16); + const view = new DataView(out.buffer); + view.setBigUint64(0, BigInt(sum)); + view.setBigUint64(8, BigInt(map.size)); + return out; +} diff --git a/ts/bench/main.ts b/ts/bench/main.ts index fd72a243f..33245e755 100644 --- a/ts/bench/main.ts +++ b/ts/bench/main.ts @@ -369,7 +369,7 @@ function render(): string { "Rust at moq-wasm's size-optimized profile, run with its input already in memory;", "copying the input in and the output out are timed apart. `hand-written` is moq's", "`js/net`, called as its own benchmarks call it, for `iter/` the same chain with ES", - "iterator helpers (`bench/iter.ts`), for `btree/` a sorted array (`bench/sorted.ts`), and", + "iterator helpers (`bench/iter.ts`), for `btree/` a sorted array (`bench/sorted.ts`), for `hash/` a `Map` (`bench/hashed.ts`), and", "for `time/` conversions `bigint`, as moq's `js/hang` converts timescales (`bench/rescale.ts`),", "and for `time/checked-add` and `time/ord` moq's own `Timestamp` (`bench/timestamp.ts`).", "Each JS route and `wasm` runs alone in a worker of its own, a fresh one each round. `A/A` is the translation again: its", diff --git a/ts/bench/moq.ts b/ts/bench/moq.ts index bfe4b3527..c175ee251 100644 --- a/ts/bench/moq.ts +++ b/ts/bench/moq.ts @@ -14,6 +14,7 @@ import * as Varint from "moq/js/net/src/varint.ts"; import { iterate } from "./iter.ts"; import { order } from "./order.ts"; +import { hashed } from "./hashed.ts"; import { push } from "./push.ts"; import { rescale } from "./rescale.ts"; import { ordered } from "./sorted.ts"; @@ -65,6 +66,8 @@ export function run(input: Uint8Array): Uint8Array | Promise { return order(selector, data); case 13: return push(data); + case 14: + return hashed(selector, data); default: throw new Error(`unknown workload ${op}`); } diff --git a/ts/bench/workloads.ts b/ts/bench/workloads.ts index 953648da0..3afdcd876 100644 --- a/ts/bench/workloads.ts +++ b/ts/bench/workloads.ts @@ -67,6 +67,12 @@ export function workloads(encode: (input: Uint8Array) => Uint8Array): Workload[] for (const count of [16, 256, 4096]) { out.push({ name: `btree/${count}`, op: "map", items: count, input: Uint8Array.of(8, 0, ...u64(BigInt(count))) }); } + // A `HashMap` of each size, through `bench.rs`'s op 14: each key inserted, looked up, and + // removed, and each key inserted alone. + for (const count of [16, 256, 4096]) { + out.push({ name: `hash/${count}`, op: "map", items: count, input: Uint8Array.of(14, 0, ...u64(BigInt(count))) }); + out.push({ name: `hash/insert/${count}`, op: "map", items: count, input: Uint8Array.of(14, 1, ...u64(BigInt(count))) }); + } // Microseconds since moq's 2020 epoch, a frame apart at 30 fps, to 90 kHz and back: // each product passes 2^53 and each quotient is back under it. `bench.rs`'s op 9 converts diff --git a/ts/fixtures/codec/expected/bench.ts b/ts/fixtures/codec/expected/bench.ts index f0c14e951..5389411ab 100644 --- a/ts/fixtures/codec/expected/bench.ts +++ b/ts/fixtures/codec/expected/bench.ts @@ -47,6 +47,8 @@ export function run(input: rt.ByteSlice): rt.ByteVec { order(selector, new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out); } else if (op === 13) { push(selector, new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out); + } else if (op === 14) { + hashed(selector, new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out); } else { decoded(decodeGroup(new rt.ByteSlice(input.data, input.start + 2, input.length - 2), liteVersion(selector), out), out); } @@ -318,6 +320,51 @@ export function ordered(data: rt.ByteSlice, out: rt.ByteVec): void { harness_putU64(out, sum); } +/** + * `count` keys, scattered as `ordered`'s, inserted into a `HashMap`, and unless `selector` + * is 1, each looked up and removed: the sum of what the lookups found, and the length left. + */ +export function hashed(selector: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + let _16!: rt.u32; + let sum!: rt.u64; + let _30!: rt.u32; + let _51!: rt.u32; + const _11: (a0: rt.u32) => rt.u32 = (i4: rt.u32): rt.u32 => { + return rt.u32Rem(rt.u32Mul(i4, 40503), 1 << 16); + }; + const count2: rt.u32 = rt.u64Low(rt.u64Min(takeU64(data)[0], 65536)); + const map: rt.HashMap = rt.hashNew() as rt.HashMap; + let iterStart: rt.u32 = 0; + const iterEnd: rt.u32 = count2; + while ((_16 = iterStart++) < iterEnd) { + const i: rt.u32 = _16; + const _22: rt.u32 = _11(i); + rt.hashInsert(map, _22, i); + } + b2: { + sum = 0; + if (selector !== 1) { + let iterStart2: rt.u32 = 0; + const iterEnd2: rt.u32 = count2; + while ((_30 = iterStart2++) < iterEnd2) { + const _40: rt.u32 = _11(rt.u32Sub(rt.u32Sub(count2, 1), _30)); + sum = rt.u64Add(sum, option_unwrapOr$11c0we2(rt.hashGet(map, _40), 0) as rt.u64); + } + let iterStart3: rt.u32 = 0; + const iterEnd3: rt.u32 = count2; + while (true) { + if (!((_51 = iterStart3++) < iterEnd3)) { + break b2; + } + const _58: rt.u32 = _11(_51); + rt.hashRemove(map, _58); + } + } + } + harness_putU64(out, sum); + putUsize(out, rt.hashLen(map)); +} + /** * `count` integers sorted, sorted by a key, searched for the greatest and least, pushed * onto a `BinaryHeap` and popped off, or collected into a `BTreeMap` or `BTreeSet`, as diff --git a/ts/fixtures/codec/src/bench.rs b/ts/fixtures/codec/src/bench.rs index 52b1cf976..d9482e68b 100644 --- a/ts/fixtures/codec/src/bench.rs +++ b/ts/fixtures/codec/src/bench.rs @@ -5,10 +5,11 @@ //! writes them, op 7 decodes frame headers as they arrive a few bytes at a time, op 8 //! keeps a `BTreeMap`, as moq-net's model keeps its state, op 9 converts timestamps //! between timescales, ops 10 and 11 add and order them as moq-net's `Timestamp` does, -//! op 12 orders integers by std's own `Ord`, and op 13 pushes onto vectors. +//! op 12 orders integers by std's own `Ord`, op 13 pushes onto vectors, and op 14 keeps +//! a `HashMap`. use std::cmp::Ordering; -use std::collections::{BTreeMap, BTreeSet, BinaryHeap, VecDeque}; +use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, VecDeque}; use crate::coding::{Decode, DecodeError, Decoder, Encode, EncodeError, Encoder, Form, varint}; use crate::harness::{decode_error, encode_error, form, lite_version, put_u64, put_usize, take_u64}; @@ -38,6 +39,7 @@ pub fn run(input: &[u8]) -> Vec { 11 => interleave(data, &mut out), 12 => order(selector, data, &mut out), 13 => push(selector, data, &mut out), + 14 => hashed(selector, data, &mut out), _ => decoded(decode_group(data, lite_version(selector), &mut out), &mut out), } out @@ -210,6 +212,29 @@ fn ordered(data: &[u8], out: &mut Vec) { put_u64(out, sum); } +/// `count` keys, scattered as `ordered`'s, inserted into a `HashMap`, and unless `selector` +/// is 1, each looked up and removed: the sum of what the lookups found, and the length left. +fn hashed(selector: u8, data: &[u8], out: &mut Vec) { + let (count, _) = take_u64(data); + let count = count.min(1 << 16) as u32; + let key = |i: u32| (i * 40503) % (1 << 16); + let mut map = HashMap::new(); + for i in 0..count { + map.insert(key(i), i); + } + let mut sum = 0u64; + if selector != 1 { + for i in 0..count { + sum += map.get(&key(count - 1 - i)).copied().unwrap_or(0) as u64; + } + for i in 0..count { + map.remove(&key(i)); + } + } + put_u64(out, sum); + put_usize(out, map.len()); +} + /// `count` integers sorted, sorted by a key, searched for the greatest and least, pushed /// onto a `BinaryHeap` and popped off, or collected into a `BTreeMap` or `BTreeSet`, as /// `selector` picks, each by `u32`'s own `Ord`: the first, middle, and last of the order, diff --git a/ts/fixtures/lang/expected/capacity.ts b/ts/fixtures/lang/expected/capacity.ts index fe21f1184..3ea560670 100644 --- a/ts/fixtures/lang/expected/capacity.ts +++ b/ts/fixtures/lang/expected/capacity.ts @@ -1,9 +1,9 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { copy$array_u64, copy$tuple2_u32_u32, copy$tuple2_u64_u8 } from "./lib.ts"; +import { copy$array_u64, copy$array_usize, copy$tuple2_u32_u32, copy$tuple2_u64_u8 } from "./lib.ts"; import * as Known from "./capacity.Known.ts"; -import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, mapOr$1t0muvr as option_mapOr$1t0muvr, mapOr$1ttssgs as option_mapOr$1ttssgs } from "./core/option.ts"; +import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, unwrapOrDefault$0301ewp as option_unwrapOrDefault$0301ewp, mapOr$1t0muvr as option_mapOr$1t0muvr, mapOr$1ttssgs as option_mapOr$1ttssgs } from "./core/option.ts"; /** A length or capacity, as four bytes. */ export function put(out: rt.ByteVec, n: rt.usize): void { @@ -1961,4 +1961,126 @@ export function collects(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void } } +/** + * std's `HashMap` and `HashSet`, made by `with_capacity` if `kind` is odd, as each pair of + * `data`'s bytes changes them: their lengths, and whether each holds at least its length, + * what `with_capacity` and `reserve` asked for, and no less past `shrink_to`, `clear`, or + * `drain` than it asked for of what there was. A `reserve` or `with_capacity` past what + * one may hold panics. + */ +export function maps(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + let _8!: [rt.HashMap, rt.HashSet]; + const _18: (a0: rt.ByteVec, a1: rt.usize[], a2: rt.usize[], a3: rt.usize[]) => undefined = (out2: rt.ByteVec, lens2: rt.usize[], caps: rt.usize[], floors2: rt.usize[]): undefined => { + let _92!: rt.usize; + let iterStart: rt.usize = 0; + const iterEnd: rt.usize = 2; + while ((_92 = iterStart++) < iterEnd) { + const i: rt.usize = _92; + if (!(i < 2)) { + rt.panic("index out of bounds: the len is " + 2 + " but the index is " + i); + } + put(out2, lens2[i]); + if (!(i < 2)) { + rt.panic("index out of bounds: the len is " + 2 + " but the index is " + i); + } + const _19: rt.usize = caps[i]; + if (!(i < 2)) { + rt.panic("index out of bounds: the len is " + 2 + " but the index is " + i); + } + const _222: rt.usize = lens2[i]; + if (!(i < 2)) { + rt.panic("index out of bounds: the len is " + 2 + " but the index is " + i); + } + rt.byteVecPush(out2, _19 >= rt.max(_222, floors2[i]) ? 1 : 0); + } + }; + const made: rt.usize = option_mapOr$1t0muvr(rt.byteSliceFirst(data), 0, (_2: rt.u8): rt.usize => { + return rt.u8Rem(_2, 24) as rt.usize; + }); + if ((kind & 1) === 1) { + _8 = [rt.hashWithCapacity(made, 58720256) as rt.HashMap, rt.hashWithCapacity(made, 469762048) as rt.HashSet] as [rt.HashMap, rt.HashSet]; + } else { + _8 = [rt.hashNew() as rt.HashMap, rt.hashNew() as rt.HashSet] as [rt.HashMap, rt.HashSet]; + } + let m: rt.HashMap = _8[0]; + let s: rt.HashSet = _8[1]; + const _22: rt.usize[] = [rt.hashLen(m), rt.hashLen(s)]; + const _27: rt.usize[] = [rt.hashCapacity(m), rt.hashCapacity(s)]; + const floor: rt.usize = rt.usizeMul(made, (kind & 1) as rt.usize); + const part = Array.from({ length: 2 }, (): rt.usize => { + return floor; + }); + _18(out, _22, _27, part); + const iter: rt.Iter = rt.byteSliceChunks(option_unwrapOrDefault$0301ewp(rt.byteSliceGetRange(data, 1)), 2); + while (true) { + const _39: rt.ByteSlice | rt.Done = iter.next(); + if (_39 === rt.DONE) { + break; + } + rt.idx(_39, 0); + const _45: [rt.u8, rt.u8] = [_39.data[_39.start], option_unwrapOr$0r2bmpr(rt.byteSliceGet(_39, 1), 0)]; + const op: rt.u8 = _45[0]; + const arg: rt.u8 = _45[1]; + const before: rt.usize[] = [rt.hashCapacity(m), rt.hashCapacity(s)]; + const lens: rt.usize[] = [rt.hashLen(m), rt.hashLen(s)]; + let floors: rt.usize[] = Array.from({ length: 2 }, (): rt.usize => { + return 0; + }); + b3: { + const _64: rt.u8 = rt.u8Rem(op, 10); + if (0 <= _64) { + if (_64 <= 2) { + rt.hashInsert(m, arg as rt.u16, [op as rt.u64, arg] as [rt.u64, rt.u8]); + rt.hashAdd(s, arg); + break b3; + } + } + if (_64 === 3) { + rt.hashRemove(m, arg as rt.u16); + rt.hashDelete(s, arg); + } else if (_64 === 4) { + const _85: rt.usize = amount(arg, lens[0], 24); + const _84: [rt.usize, rt.usize] = [_85, amount(arg, lens[1], 1)]; + const a: rt.usize = _85; + const b: rt.usize = _84[1]; + rt.hashReserve(m, a, 58720256); + rt.hashReserve(s, b, 469762048); + floors = [rt.usizeAdd(lens[0], a), rt.usizeAdd(lens[1], b)]; + } else if (_64 === 5) { + const min: rt.usize = rt.u8Rem(arg, 20) as rt.usize; + rt.hashShrinkTo(m, min); + rt.hashShrinkTo(s, min); + floors = [rt.min(before[0], rt.max(lens[0], min)), rt.min(before[1], rt.max(lens[1], min))]; + } else if (_64 === 6) { + rt.hashShrinkTo(m, 0); + rt.hashShrinkTo(s, 0); + } else if (_64 === 7) { + rt.hashClear(m); + rt.hashClear(s); + floors = before; + } else if (_64 === 8) { + rt.hashRetain(m, (k: rt.u16, _3: [rt.u64, rt.u8]): boolean => { + return rt.u16Rem(k, 3) !== rt.u8Rem(arg, 3) as rt.u16; + }); + rt.hashRetain(s, (k2: rt.u8): boolean => { + return rt.u8Rem(k2, 3) !== rt.u8Rem(arg, 3); + }); + m = rt.hashClone(m, undefined, copy$tuple2_u64_u8); + s = rt.hashClone(s, undefined, undefined); + } else { + rt.hashDrain(m); + rt.hashDrainKeys(s); + floors = before; + } + } + const part2 = [rt.hashLen(m), rt.hashLen(s)]; + const part3 = [rt.hashCapacity(m), rt.hashCapacity(s)]; + const part4 = copy$array_usize(floors); + _18(out, part2, part3, part4); + } + if ((kind & 128) !== 0) { + put(out, rt.hashLen(rt.hashWithCapacity(rt.usizeShr(4294967295, kind & 3), 117440512, rt.hashing(rt.hash64, rt.u64Eq)) as rt.HashSet)); + } +} + //# sourceMappingURL=capacity.ts.map diff --git a/ts/fixtures/lang/expected/collections.ts b/ts/fixtures/lang/expected/collections.ts index d7eb7e3a4..a32b5ee7c 100644 --- a/ts/fixtures/lang/expected/collections.ts +++ b/ts/fixtures/lang/expected/collections.ts @@ -124,7 +124,7 @@ export function hashMaps(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void rt.hashClear(map); rt.byteVecPush(out, rt.hashLen(map) & 0xff); } else if (_4 === 1) { - const map2: rt.HashMap = rt.hashNew(rt.hashing(rt.hash64, rt.u64Eq)) as rt.HashMap; + const map2: rt.HashMap = rt.hashWithCapacity(4, 58720256, rt.hashing(rt.hash64, rt.u64Eq)) as rt.HashMap; const iter3: rt.Iter<[rt.usize, rt.u8]> = rt.iterEnumerate(rt.iterSlice(rt.byteSliceView(data))); while (true) { const _110: [rt.usize, rt.u8] | rt.Done = iter3.next(); @@ -306,8 +306,8 @@ export function hashMaps(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void rt.byteVecPush(out, claim(copy2, rt.u8Rem(b5, 8), b5 as rt.u16)); } const stamped: rt.HashMap = rt.hashNew() as rt.HashMap; - stamp(stamped); - stamp(stamped); + rt.hashShrinkTo(stamp(stamped), 0); + rt.hashReserve(stamp(stamped), 2, 234881024); rt.byteVecPush(out, rt.hashLen(stamped) & 0xff); const extended: rt.HashMap = rt.hashNew() as rt.HashMap; rt.hashExtend(extended, rt.iterMap(rt.hashIter(bytes), copy$tuple2_u8_collections_Tally), undefined, undefined, undefined, 234881024); diff --git a/ts/fixtures/lang/expected/core/option.ts b/ts/fixtures/lang/expected/core/option.ts index edf47dcfc..d560b69e0 100644 --- a/ts/fixtures/lang/expected/core/option.ts +++ b/ts/fixtures/lang/expected/core/option.ts @@ -242,6 +242,13 @@ export function unwrapOrDefault$0wx44sw(self: rt.ByteVec | undefined): rt.ByteVe return self; } +export function unwrapOrDefault$0301ewp(self: rt.ByteSlice | undefined): rt.ByteSlice { + if (self === undefined) { + return rt.byteSliceFrom(new Uint8Array(0)); + } + return self; +} + export function unwrapOrDefault$0k1p6nh(self: [rt.u64 | undefined, defaults_Made.Made] | undefined): [rt.u64 | undefined, defaults_Made.Made] { if (self === undefined) { return [undefined, defaults_Made.default$Default()]; diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 9ad40226a..b73620da1 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -8,7 +8,7 @@ import { atomics } from "./atomics.ts"; import { borrows as borrows_borrows, guards, maps as borrows_maps, tries } from "./borrows.ts"; import { bounds as bounds_bounds } from "./bounds.ts"; import { boxes, places as boxes_places } from "./boxes.ts"; -import { vecs as capacity_vecs, deques as capacity_deques, heaps as capacity_heaps, collects } from "./capacity.ts"; +import { vecs as capacity_vecs, deques as capacity_deques, heaps as capacity_heaps, collects, maps as capacity_maps } from "./capacity.ts"; import { captures } from "./captures.ts"; import { cells as cells_cells } from "./cells.ts"; import { chars as chars_chars } from "./chars.ts"; @@ -273,7 +273,7 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) capacity_deques(a0153, a1153.data, a1153.start, a1153.length, a2153); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::heaps", (a0154: rt.u8, a1154: rt.ByteSlice, a2154: rt.ByteVec): undefined => { capacity_heaps(a0154, a1154.data, a1154.start, a1154.length, a2154); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::maps", capacity_maps as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } /** Run one case. */ diff --git a/ts/fixtures/lang/expected/lib.ts b/ts/fixtures/lang/expected/lib.ts index 0998affce..4329b29fa 100644 --- a/ts/fixtures/lang/expected/lib.ts +++ b/ts/fixtures/lang/expected/lib.ts @@ -299,6 +299,10 @@ export function copy$array_unit(v: undefined[]): undefined[] { return v.slice(); } +export function copy$array_usize(v: rt.usize[]): rt.usize[] { + return v.slice(); +} + export function copy$collections_Quirky(v: Quirky.Quirky): Quirky.Quirky { return { _0: v._0, _1: v._1 }; } diff --git a/ts/fixtures/lang/src/capacity.rs b/ts/fixtures/lang/src/capacity.rs index 76e49af0e..96a12837a 100644 --- a/ts/fixtures/lang/src/capacity.rs +++ b/ts/fixtures/lang/src/capacity.rs @@ -2,9 +2,10 @@ //! grows, shrinks, remakes, or reads one, of elements of each size its growth tells apart: //! a byte, at most 1 KiB, past it, and none. std fixes it exactly only through steps its //! doubling decides, such as pushes, `with_capacity`, `reserve`, and `shrink_to`; past -//! any other step, a case checks only what std guarantees. +//! any other step, a case checks only what std guarantees. A `HashMap`'s and a +//! `HashSet`'s it checks only by std's guarantees. -use std::collections::{BinaryHeap, VecDeque}; +use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; use std::mem; /// A length or capacity, as four bytes. @@ -460,3 +461,82 @@ pub fn collects(kind: u8, data: &[u8], out: &mut Vec) { } } } + +/// std's `HashMap` and `HashSet`, made by `with_capacity` if `kind` is odd, as each pair of +/// `data`'s bytes changes them: their lengths, and whether each holds at least its length, +/// what `with_capacity` and `reserve` asked for, and no less past `shrink_to`, `clear`, or +/// `drain` than it asked for of what there was. A `reserve` or `with_capacity` past what +/// one may hold panics. +pub fn maps(kind: u8, data: &[u8], out: &mut Vec) { + let made = data.first().map_or(0, |&b| usize::from(b % 24)); + let (mut m, mut s): (HashMap, HashSet) = match kind & 1 { + 1 => (HashMap::with_capacity(made), HashSet::with_capacity(made)), + _ => (HashMap::new(), HashSet::new()), + }; + let floor = made * usize::from(kind & 1); + let checks = |out: &mut Vec, lens: [usize; 2], caps: [usize; 2], floors: [usize; 2]| { + for i in 0..2 { + put(out, lens[i]); + out.push(u8::from(caps[i] >= lens[i].max(floors[i]))); + } + }; + checks(out, [m.len(), s.len()], [m.capacity(), s.capacity()], [floor; 2]); + for step in data.get(1..).unwrap_or_default().chunks(2) { + let (op, arg) = (step[0], step.get(1).copied().unwrap_or(0)); + let before = [m.capacity(), s.capacity()]; + let lens = [m.len(), s.len()]; + let mut floors = [0; 2]; + match op % 10 { + 0..=2 => { + m.insert(u16::from(arg), (u64::from(op), arg)); + s.insert(arg); + } + 3 => { + m.remove(&u16::from(arg)); + s.remove(&arg); + } + 4 => { + let (a, b) = ( + amount(arg, lens[0], size_of::<(u16, (u64, u8))>()), + amount(arg, lens[1], 1), + ); + m.reserve(a); + s.reserve(b); + floors = [lens[0] + a, lens[1] + b]; + } + 5 => { + let min = usize::from(arg % 20); + m.shrink_to(min); + s.shrink_to(min); + floors = [before[0].min(lens[0].max(min)), before[1].min(lens[1].max(min))]; + } + 6 => { + m.shrink_to_fit(); + s.shrink_to_fit(); + } + 7 => { + m.clear(); + s.clear(); + floors = before; + } + 8 => { + // By key, as which keys every other one would keep follows iteration order. + m.retain(|k, _| k % 3 != u16::from(arg % 3)); + s.retain(|k| k % 3 != arg % 3); + // std promises nothing of a clone's capacity. + m = m.clone(); + s = s.clone(); + } + _ => { + drop(m.drain()); + drop(s.drain()); + floors = before; + } + } + checks(out, [m.len(), s.len()], [m.capacity(), s.capacity()], floors); + } + if kind & 0x80 != 0 { + let huge: HashSet = HashSet::with_capacity(usize::MAX >> (kind & 3)); + put(out, huge.len()); + } +} diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 11c181f32..e5332b1b1 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -282,6 +282,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("capacity::deques", capacity::deques), ("capacity::heaps", capacity::heaps), ("capacity::collects", capacity::collects), + ("capacity::maps", capacity::maps), ] } diff --git a/ts/fixtures/stock/Cargo.toml b/ts/fixtures/stock/Cargo.toml index 86b84d636..53f57845e 100644 --- a/ts/fixtures/stock/Cargo.toml +++ b/ts/fixtures/stock/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stock-fixture" -description = "Calls into bytes, rand, and tracing, which rust2-ts maps itself" +description = "Calls into bytes, hashbrown, rand, and tracing, which rust2-ts maps itself, and hashlink" version = "0.0.0" edition.workspace = true license.workspace = true @@ -14,6 +14,8 @@ crate-type = ["lib", "cdylib"] bytes = "1.12.1" # The native side's source of randomness on wasm32, which its tests select. getrandom = "0.4.3" +hashbrown = "0.17.1" +hashlink = "0.12.2" rand = "0.10.3" tracing = "0.1.44" diff --git a/ts/fixtures/stock/expected/core/option.ts b/ts/fixtures/stock/expected/core/option.ts index 0fb1b6278..718575169 100644 --- a/ts/fixtures/stock/expected/core/option.ts +++ b/ts/fixtures/stock/expected/core/option.ts @@ -9,4 +9,25 @@ export function unwrapOr$0r2bmpr(self: rt.u8 | undefined, default_: rt.u8): rt.u return self; } +export function mapOr$1787w44(self: rt.Place | undefined, default_: rt.usize, f: (a0: rt.Place) => rt.usize): rt.usize { + if (self === undefined) { + return default_; + } + return f(self); +} + +export function mapOr$0iaogpf(self: [rt.u16, rt.u32] | undefined, default_: rt.usize, f: (a0: [rt.u16, rt.u32]) => rt.usize): rt.usize { + if (self === undefined) { + return default_; + } + return f(self); +} + +export function mapOr$1vcgixw(self: rt.u32 | undefined, default_: rt.usize, f: (a0: rt.u32) => rt.usize): rt.usize { + if (self === undefined) { + return default_; + } + return f(self); +} + //# sourceMappingURL=option.ts.map diff --git a/ts/fixtures/stock/expected/harness.ts b/ts/fixtures/stock/expected/harness.ts index 4209af111..383486577 100644 --- a/ts/fixtures/stock/expected/harness.ts +++ b/ts/fixtures/stock/expected/harness.ts @@ -3,6 +3,7 @@ import * as rt from "./rt.ts"; import { bytesMut, reserves, reads, writes, edits } from "./buf.ts"; import * as Owner from "./harness.Owner.ts"; +import { map as maps_map, table, capacity, linked } from "./maps.ts"; /** * Each case's name and what it runs. The first byte picks one modulo their count, so a @@ -23,7 +24,11 @@ export function cases(): [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefine edits(a08.data, a08.start, a08.length, a18); }] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["buf::reserves", (a09: rt.ByteSlice, a19: rt.ByteVec): undefined => { reserves(a09.data, a09.start, a09.length, a19); - }] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined]]; + }] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["maps::map", maps_map as (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["maps::table", (a011: rt.ByteSlice, a111: rt.ByteVec): undefined => { + table(a011.data, a011.start, a011.length, a111); + }] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["maps::capacity", (a012: rt.ByteSlice, a112: rt.ByteVec): undefined => { + capacity(a012.data, a012.start, a012.length, a112); + }] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["maps::linked", linked as (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined]]; } /** Run one case. */ diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.DropFilteredValues.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.DropFilteredValues.ts new file mode 100644 index 000000000..f765b87e8 --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.DropFilteredValues.ts @@ -0,0 +1,36 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32, detachNode$u16_u32, pushFree$u16_u32 } from "../hashlink/linked_hash_map.ts"; +import * as Node from "../hashlink/linked_hash_map.Node.ts"; + +export class DropFilteredValues { + free: rt.Place | undefined>; + curFree: Node.Node | undefined; + + constructor(free: rt.Place | undefined>, curFree: Node.Node | undefined) { + this.free = free; + this.curFree = curFree; + } +} + +export function dropLater$u16_u32(self: DropFilteredValues, node: Node.Node): void { + detachNode$u16_u32(node); + pushFree$u16_u32(rt.place(() => self.curFree, (value) => self.curFree = value), node); +} + +export function drop$ops_Drop_u16_u32(self: DropFilteredValues): void { + let endFree!: Node.Node | undefined; + while (true) { + endFree = self.curFree; + if (!(self.curFree !== self.free.v)) { + break; + } + const curFree: Node.Node = asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.curFree); + Node.takeEntry$u16_u32(curFree); + self.curFree = curFree.links.at0; + } + self.free.v = endFree; +} + +//# sourceMappingURL=linked_hash_map.DropFilteredValues.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.LinkedHashMap.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.LinkedHashMap.ts new file mode 100644 index 000000000..aa64595fe --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.LinkedHashMap.ts @@ -0,0 +1,190 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { drop$hashlink_linked_hash_map_DropFilteredValues_u16_u32 as lib_drop$hashlink_linked_hash_map_DropFilteredValues_u16_u32 } from "../lib.ts"; +import { asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32, dropValueNodes$u16_u32, dropFreeNodes$u16_u32, hashNode$hash_RandomState_u16_u32 } from "../hashlink/linked_hash_map.ts"; +import * as DropFilteredValues from "../hashlink/linked_hash_map.DropFilteredValues.ts"; +import * as Node from "../hashlink/linked_hash_map.Node.ts"; +import * as RawEntryBuilderMut from "../hashlink/linked_hash_map.RawEntryBuilderMut.ts"; +import type * as RawEntryMut from "../hashlink/linked_hash_map.RawEntryMut.ts"; +import * as RawOccupiedEntryMut from "../hashlink/linked_hash_map.RawOccupiedEntryMut.ts"; +import * as RawVacantEntryMut from "../hashlink/linked_hash_map.RawVacantEntryMut.ts"; + +/** + * A version of `HashMap` that has a user controllable order for its entries. + * + * It achieves this by keeping its entries in an internal linked list and using a `HashMap` to + * point at nodes in this linked list. + * + * The order of entries defaults to "insertion order", but the user can also modify the order of + * existing entries by manually moving them to the front or back. + * + * There are two kinds of methods that modify the order of the internal list: + * + * * Methods that have names like `to_front` and `to_back` will unsurprisingly move an existing + * entry to the front or back + * * Methods that have the word `insert` will insert a new entry ot the back of the list, and if + * that method might replace an entry, that method will *also move that existing entry to the + * back*. + */ +export class LinkedHashMap { + table: rt.HashTable>; + hashBuilder: S; + values: Node.Node | undefined; + free: Node.Node | undefined; + + constructor(table: rt.HashTable>, hashBuilder: S, values: Node.Node | undefined, free: Node.Node | undefined) { + this.table = table; + this.hashBuilder = hashBuilder; + this.values = values; + this.free = free; + } +} + +export function withHasher$u16_u32_hash_RandomState(hashBuilder: rt.RandomState): LinkedHashMap { + return new LinkedHashMap(rt.hashNew() as rt.HashTable>, hashBuilder, undefined, undefined); +} + +export function len$u16_u32_hash_RandomState(self: LinkedHashMap): rt.usize { + return rt.hashLen(self.table); +} + +export function isEmpty$u16_u32_hash_RandomState(self: LinkedHashMap): boolean { + return len$u16_u32_hash_RandomState(self) === 0; +} + +export function front$u16_u32_hash_RandomState(self: LinkedHashMap): [rt.u16, rt.u32] | undefined { + if (!isEmpty$u16_u32_hash_RandomState(self)) { + const _9: [rt.u16, rt.u32] = Node.entryRef$u16_u32(asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.values).links.at0 as Node.Node); + return [_9[0], _9[1]] as [rt.u16, rt.u32]; + } + return undefined; +} + +export function back$u16_u32_hash_RandomState(self: LinkedHashMap): [rt.u16, rt.u32] | undefined { + if (!isEmpty$u16_u32_hash_RandomState(self)) { + const _10: [rt.u16, rt.u32] = Node.entryRef$u16_u32(asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.values).links.at4); + return [_10[0], _10[1]] as [rt.u16, rt.u32]; + } + return undefined; +} + +export function retain$u16_u32_hash_RandomState_maps_linked_closure8(self: LinkedHashMap, f: (a0: rt.u16, a1: rt.Place) => boolean): void { + const free: Node.Node | undefined = self.free; + const dropFilteredValues: DropFilteredValues.DropFilteredValues = new DropFilteredValues.DropFilteredValues(rt.place(() => self.free, (value) => self.free = value), free); + rt.tableRetainMut(self.table, (_2: rt.Place>): boolean => { + const node: Node.Node = _2.v; + const _6: [rt.u16, rt.u32] = Node.entryMut$u16_u32(node); + if (!f(_6[0], rt.place(() => _6[1], (value2) => _6[1] = value2))) { + DropFilteredValues.dropLater$u16_u32(dropFilteredValues, node); + return false; + } + return true; + }); + lib_drop$hashlink_linked_hash_map_DropFilteredValues_u16_u32(dropFilteredValues); +} + +export function capacity$u16_u32_hash_RandomState(self: LinkedHashMap): rt.usize { + return rt.hashCapacity(self.table); +} + +/** + * Inserts the given key / value pair at the *back* of the internal linked list. + * + * Returns the previously set value, if one existed prior to this call. After this call, + * calling `LinkedHashMap::back` will return a reference to this key / value pair. + */ +export function insert$u16_u32_hash_RandomState(self: LinkedHashMap, k: rt.u16, v: rt.u32): rt.u32 | undefined { + const _4: RawEntryMut.RawEntryMut = RawEntryBuilderMut.fromKey$u16_u32_hash_RandomState_u16(rawEntryMut$u16_u32_hash_RandomState(self), k); + if (_4.tag === "Occupied") { + const occupied: RawOccupiedEntryMut.RawOccupiedEntryMut = _4._0; + RawOccupiedEntryMut.toBack$u16_u32_hash_RandomState(occupied); + return RawOccupiedEntryMut.replaceValue$u16_u32_hash_RandomState(occupied, v); + } + RawVacantEntryMut.insert$u16_u32_hash_RandomState(_4._0, k, v); + return undefined; +} + +/** + * Inserts the given key / value pair at the *front* of the internal linked list. + * + * Returns the previously set value, if one existed prior to this call. After this call, + * calling `LinkedHashMap::front` will return a reference to this key / value pair. + */ +export function insertFront$u16_u32_hash_RandomState(self: LinkedHashMap, k: rt.u16, v: rt.u32): rt.u32 | undefined { + const _4: RawEntryMut.RawEntryMut = RawEntryBuilderMut.fromKey$u16_u32_hash_RandomState_u16(rawEntryMut$u16_u32_hash_RandomState(self), k); + if (_4.tag === "Occupied") { + const occupied: RawOccupiedEntryMut.RawOccupiedEntryMut = _4._0; + RawOccupiedEntryMut.toFront$u16_u32_hash_RandomState(occupied); + return RawOccupiedEntryMut.replaceValue$u16_u32_hash_RandomState(occupied, v); + } + RawOccupiedEntryMut.toFront$u16_u32_hash_RandomState(RawVacantEntryMut.insertEntry$u16_u32_hash_RandomState(_4._0, k, v)); + return undefined; +} + +export function remove$u16_u32_hash_RandomState_u16(self: LinkedHashMap, k: rt.u16): rt.u32 | undefined { + const _3: RawEntryMut.RawEntryMut = RawEntryBuilderMut.fromKey$u16_u32_hash_RandomState_u16(rawEntryMut$u16_u32_hash_RandomState(self), k); + if (_3.tag === "Occupied") { + return RawOccupiedEntryMut.remove$u16_u32_hash_RandomState(_3._0); + } + return undefined; +} + +export function popFront$u16_u32_hash_RandomState(self: LinkedHashMap): [rt.u16, rt.u32] | undefined { + if (!isEmpty$u16_u32_hash_RandomState(self)) { + const front: Node.Node = asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.values).links.at0 as Node.Node; + const hash: rt.u64 = hashNode$hash_RandomState_u16_u32(self.hashBuilder, front); + const _9: RawEntryMut.RawEntryMut = RawEntryBuilderMut.fromHash$u16_u32_hash_RandomState_hashlink_LinkedHashMap_K_V_S_pop_front_closure0_u16_u32_hash_RandomState(rawEntryMut$u16_u32_hash_RandomState(self), hash, (k: rt.u16): boolean => { + return k === Node.keyRef$u16_u32(front); + }); + if (_9.tag === "Occupied") { + return RawOccupiedEntryMut.removeEntry$u16_u32_hash_RandomState(_9._0); + } + return undefined; + } + return undefined; +} + +export function popBack$u16_u32_hash_RandomState(self: LinkedHashMap): [rt.u16, rt.u32] | undefined { + if (!isEmpty$u16_u32_hash_RandomState(self)) { + const back: Node.Node = asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.values).links.at4; + const hash: rt.u64 = hashNode$hash_RandomState_u16_u32(self.hashBuilder, back); + const _9: RawEntryMut.RawEntryMut = RawEntryBuilderMut.fromHash$u16_u32_hash_RandomState_hashlink_LinkedHashMap_K_V_S_pop_back_closure0_u16_u32_hash_RandomState(rawEntryMut$u16_u32_hash_RandomState(self), hash, (k: rt.u16): boolean => { + return k === Node.keyRef$u16_u32(back); + }); + if (_9.tag === "Occupied") { + return RawOccupiedEntryMut.removeEntry$u16_u32_hash_RandomState(_9._0); + } + return undefined; + } + return undefined; +} + +/** + * If an entry with this key exists, move it to the back of the list and return a reference to + * the value. + */ +export function toBack$u16_u32_hash_RandomState_u16(self: LinkedHashMap, k: rt.u16): rt.Place | undefined { + const _3: RawEntryMut.RawEntryMut = RawEntryBuilderMut.fromKey$u16_u32_hash_RandomState_u16(rawEntryMut$u16_u32_hash_RandomState(self), k); + if (_3.tag === "Occupied") { + const occupied: RawOccupiedEntryMut.RawOccupiedEntryMut = _3._0; + RawOccupiedEntryMut.toBack$u16_u32_hash_RandomState(occupied); + return RawOccupiedEntryMut.intoMut$u16_u32_hash_RandomState(occupied); + } + return undefined; +} + +export function rawEntryMut$u16_u32_hash_RandomState(self: LinkedHashMap): RawEntryBuilderMut.RawEntryBuilderMut { + return { map: self }; +} + +export function drop$ops_Drop_u16_u32_hash_RandomState(self: LinkedHashMap): void { + if (self.values !== undefined) { + const values: Node.Node = self.values; + dropValueNodes$u16_u32(values); + values; + } + dropFreeNodes$u16_u32(self.free); +} + +//# sourceMappingURL=linked_hash_map.LinkedHashMap.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.Links.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.Links.ts new file mode 100644 index 000000000..c812666b1 --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.Links.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +import type * as Node from "../hashlink/linked_hash_map.Node.ts"; + +export type Links = { at0: Node.Node | undefined; at4: Node.Node }; + +//# sourceMappingURL=linked_hash_map.Links.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.Node.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.Node.ts new file mode 100644 index 000000000..b04667138 --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.Node.ts @@ -0,0 +1,32 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { copy$tuple2_u16_u32 } from "../lib.ts"; +import type * as Links from "../hashlink/linked_hash_map.Links.ts"; + +export interface Node { + entry: [K, V]; + links: Links.Links; +} + +export function putEntry$u16_u32(self: Node, entry: [rt.u16, rt.u32]): void { + rt.ptrWrite(self.entry, copy$tuple2_u16_u32(entry)); +} + +export function entryRef$u16_u32(self: Node): [rt.u16, rt.u32] { + return self.entry; +} + +export function keyRef$u16_u32(self: Node): rt.u16 { + return self.entry[0]; +} + +export function entryMut$u16_u32(self: Node): [rt.u16, rt.u32] { + return self.entry; +} + +export function takeEntry$u16_u32(self: Node): [rt.u16, rt.u32] { + return copy$tuple2_u16_u32(self.entry); +} + +//# sourceMappingURL=linked_hash_map.Node.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawEntryBuilderMut.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawEntryBuilderMut.ts new file mode 100644 index 000000000..d8954d34a --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawEntryBuilderMut.ts @@ -0,0 +1,74 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { hashKey$hash_RandomState_u16 } from "../hashlink/linked_hash_map.ts"; +import type * as LinkedHashMap from "../hashlink/linked_hash_map.LinkedHashMap.ts"; +import * as Node from "../hashlink/linked_hash_map.Node.ts"; +import type * as RawEntryMut from "../hashlink/linked_hash_map.RawEntryMut.ts"; + +export interface RawEntryBuilderMut { + map: LinkedHashMap.LinkedHashMap; +} + +export function fromKey$u16_u32_hash_RandomState_u16(self: RawEntryBuilderMut, k: rt.u16): RawEntryMut.RawEntryMut { + return fromKeyHashedNocheck$u16_u32_hash_RandomState_u16(self, hashKey$hash_RandomState_u16(self.map.hashBuilder, k), k); +} + +export function fromKeyHashedNocheck$u16_u32_hash_RandomState_u16(self: RawEntryBuilderMut, hash: rt.u64, k: rt.u16): RawEntryMut.RawEntryMut { + return fromHash$u16_u32_hash_RandomState_hashlink_linked_hash_map_RawEntryBuilderMut_K_V_S_from_key_hashed_nocheck_closure0_u16_u32_hash_RandomState_u16(self, hash, (o: rt.u16): boolean => { + return k === o; + }); +} + +export function fromHash$u16_u32_hash_RandomState_hashlink_LinkedHashMap_K_V_S_pop_back_closure0_u16_u32_hash_RandomState(self: RawEntryBuilderMut, hash: rt.u64, isMatch: (a0: rt.u16) => boolean): RawEntryMut.RawEntryMut { + let captured = isMatch; + const entry: rt.Result>, rt.TableEntry>> = rt.tableFindEntry(self.map.table, hash, (k: Node.Node): boolean => { + return captured(Node.keyRef$u16_u32(k)); + }); + if (entry.ok) { + const occupied: rt.TableEntry> = entry.value; + const borrowed3 = self.map; + const borrowed4 = self.map; + return { tag: "Occupied", _0: { hashBuilder: self.map.hashBuilder, free: rt.place(() => borrowed3.free, (value3) => borrowed3.free = value3), values: rt.place(() => borrowed4.values, (value4) => borrowed4.values = value4), entry: occupied } } as RawEntryMut.RawEntryMut; + } + const absent: rt.TableEntry> = entry.error; + const borrowed = self.map; + const borrowed2 = self.map; + return { tag: "Vacant", _0: { hashBuilder: self.map.hashBuilder, values: rt.place(() => borrowed.values, (value) => borrowed.values = value), free: rt.place(() => borrowed2.free, (value2) => borrowed2.free = value2), entry: absent } } as RawEntryMut.RawEntryMut; +} + +export function fromHash$u16_u32_hash_RandomState_hashlink_LinkedHashMap_K_V_S_pop_front_closure0_u16_u32_hash_RandomState(self: RawEntryBuilderMut, hash: rt.u64, isMatch: (a0: rt.u16) => boolean): RawEntryMut.RawEntryMut { + let captured = isMatch; + const entry: rt.Result>, rt.TableEntry>> = rt.tableFindEntry(self.map.table, hash, (k: Node.Node): boolean => { + return captured(Node.keyRef$u16_u32(k)); + }); + if (entry.ok) { + const occupied: rt.TableEntry> = entry.value; + const borrowed3 = self.map; + const borrowed4 = self.map; + return { tag: "Occupied", _0: { hashBuilder: self.map.hashBuilder, free: rt.place(() => borrowed3.free, (value3) => borrowed3.free = value3), values: rt.place(() => borrowed4.values, (value4) => borrowed4.values = value4), entry: occupied } } as RawEntryMut.RawEntryMut; + } + const absent: rt.TableEntry> = entry.error; + const borrowed = self.map; + const borrowed2 = self.map; + return { tag: "Vacant", _0: { hashBuilder: self.map.hashBuilder, values: rt.place(() => borrowed.values, (value) => borrowed.values = value), free: rt.place(() => borrowed2.free, (value2) => borrowed2.free = value2), entry: absent } } as RawEntryMut.RawEntryMut; +} + +export function fromHash$u16_u32_hash_RandomState_hashlink_linked_hash_map_RawEntryBuilderMut_K_V_S_from_key_hashed_nocheck_closure0_u16_u32_hash_RandomState_u16(self: RawEntryBuilderMut, hash: rt.u64, isMatch: (a0: rt.u16) => boolean): RawEntryMut.RawEntryMut { + let captured = isMatch; + const entry: rt.Result>, rt.TableEntry>> = rt.tableFindEntry(self.map.table, hash, (k: Node.Node): boolean => { + return captured(Node.keyRef$u16_u32(k)); + }); + if (entry.ok) { + const occupied: rt.TableEntry> = entry.value; + const borrowed3 = self.map; + const borrowed4 = self.map; + return { tag: "Occupied", _0: { hashBuilder: self.map.hashBuilder, free: rt.place(() => borrowed3.free, (value3) => borrowed3.free = value3), values: rt.place(() => borrowed4.values, (value4) => borrowed4.values = value4), entry: occupied } } as RawEntryMut.RawEntryMut; + } + const absent: rt.TableEntry> = entry.error; + const borrowed = self.map; + const borrowed2 = self.map; + return { tag: "Vacant", _0: { hashBuilder: self.map.hashBuilder, values: rt.place(() => borrowed.values, (value) => borrowed.values = value), free: rt.place(() => borrowed2.free, (value2) => borrowed2.free = value2), entry: absent } } as RawEntryMut.RawEntryMut; +} + +//# sourceMappingURL=linked_hash_map.RawEntryBuilderMut.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawEntryMut.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawEntryMut.ts new file mode 100644 index 000000000..6999804ec --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawEntryMut.ts @@ -0,0 +1,10 @@ +// Generated by rust2-ts. Do not edit. + +import type * as RawOccupiedEntryMut from "../hashlink/linked_hash_map.RawOccupiedEntryMut.ts"; +import type * as RawVacantEntryMut from "../hashlink/linked_hash_map.RawVacantEntryMut.ts"; + +export type RawEntryMut = + | { tag: "Occupied"; _0: RawOccupiedEntryMut.RawOccupiedEntryMut } + | { tag: "Vacant"; _0: RawVacantEntryMut.RawVacantEntryMut }; + +//# sourceMappingURL=linked_hash_map.RawEntryMut.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawOccupiedEntryMut.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawOccupiedEntryMut.ts new file mode 100644 index 000000000..847e9e755 --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawOccupiedEntryMut.ts @@ -0,0 +1,51 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32, attachBefore$u16_u32, detachNode$u16_u32, removeNode$u16_u32 } from "../hashlink/linked_hash_map.ts"; +import * as Node from "../hashlink/linked_hash_map.Node.ts"; + +export interface RawOccupiedEntryMut { + hashBuilder: rt.Ptr; + free: rt.Place | undefined>; + values: rt.Place | undefined>; + entry: rt.TableEntry>; +} + +export function intoMut$u16_u32_hash_RandomState(self: RawOccupiedEntryMut): rt.Place { + return intoKeyValue$u16_u32_hash_RandomState(self)[1]; +} + +export function intoKeyValue$u16_u32_hash_RandomState(self: RawOccupiedEntryMut): [rt.Place, rt.Place] { + const _7: [rt.u16, rt.u32] = Node.entryMut$u16_u32(self.entry.v); + return [rt.place(() => _7[0], (value) => _7[0] = value), rt.place(() => _7[1], (value2) => _7[1] = value2)] as [rt.Place, rt.Place]; +} + +export function toBack$u16_u32_hash_RandomState(self: RawOccupiedEntryMut): void { + const node: Node.Node = self.entry.v; + detachNode$u16_u32(node); + attachBefore$u16_u32(node, asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.values.v)); +} + +export function toFront$u16_u32_hash_RandomState(self: RawOccupiedEntryMut): void { + const node: Node.Node = self.entry.v; + detachNode$u16_u32(node); + attachBefore$u16_u32(node, asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.values.v).links.at0 as Node.Node); +} + +export function replaceValue$u16_u32_hash_RandomState(self: RawOccupiedEntryMut, value: rt.u32): rt.u32 { + const _7: [rt.u16, rt.u32] = Node.entryMut$u16_u32(self.entry.v); + const old = _7[1]; + _7[1] = value; + return old; +} + +export function remove$u16_u32_hash_RandomState(self: RawOccupiedEntryMut): rt.u32 { + return removeEntry$u16_u32_hash_RandomState(self)[1]; +} + +export function removeEntry$u16_u32_hash_RandomState(self: RawOccupiedEntryMut): [rt.u16, rt.u32] { + const node: Node.Node = rt.tableRemove(self.entry)[0]; + return removeNode$u16_u32(self.free, node); +} + +//# sourceMappingURL=linked_hash_map.RawOccupiedEntryMut.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawVacantEntryMut.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawVacantEntryMut.ts new file mode 100644 index 000000000..46798e8e1 --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.RawVacantEntryMut.ts @@ -0,0 +1,69 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32, ensureGuardNode$u16_u32, attachBefore$u16_u32, allocateNode$u16_u32, hashKey$hash_RandomState_u16 } from "../hashlink/linked_hash_map.ts"; +import * as Node from "../hashlink/linked_hash_map.Node.ts"; +import * as RawOccupiedEntryMut from "../hashlink/linked_hash_map.RawOccupiedEntryMut.ts"; + +export interface RawVacantEntryMut { + hashBuilder: rt.Ptr; + values: rt.Place | undefined>; + free: rt.Place | undefined>; + entry: rt.TableEntry>; +} + +export function insert$u16_u32_hash_RandomState(self: RawVacantEntryMut, key: rt.u16, value: rt.u32): [rt.Place, rt.Place] { + return insertHashedNocheck$u16_u32_hash_RandomState(self, hashKey$hash_RandomState_u16(self.hashBuilder as rt.RandomState, key), key, value); +} + +export function insertHashedNocheck$u16_u32_hash_RandomState(self: RawVacantEntryMut, hash: rt.u64, key: rt.u16, value: rt.u32): [rt.Place, rt.Place] { + const hashBuilder: rt.RandomState = self.hashBuilder as rt.RandomState; + return insertWithHasher$u16_u32_hash_RandomState_hashlink_linked_hash_map_RawVacantEntryMut_K_V_S_insert_hashed_nocheck_closure0_u16_u32_hash_RandomState(self, hash, key, value, (k: rt.u16): rt.u64 => { + return hashKey$hash_RandomState_u16(hashBuilder, k); + }); +} + +export function insertWithHasher$u16_u32_hash_RandomState_hashlink_linked_hash_map_RawVacantEntryMut_K_V_S_insert_hashed_nocheck_closure0_u16_u32_hash_RandomState(self: RawVacantEntryMut, hash: rt.u64, key: rt.u16, value: rt.u32, hasher: (a0: rt.u16) => rt.u64): [rt.Place, rt.Place] { + return RawOccupiedEntryMut.intoKeyValue$u16_u32_hash_RandomState(insertEntryWithHasher$u16_u32_hash_RandomState_hashlink_linked_hash_map_RawVacantEntryMut_K_V_S_insert_hashed_nocheck_closure0_u16_u32_hash_RandomState(self, hash, key, value, hasher)); +} + +export function insertEntryWithHasher$u16_u32_hash_RandomState_hashlink_linked_hash_map_RawVacantEntryMut_K_V_S_insert_entry_hashed_nocheck_closure0_u16_u32_hash_RandomState(self: RawVacantEntryMut, hash: rt.u64, key: rt.u16, value: rt.u32, hasher: (a0: rt.u16) => rt.u64): RawOccupiedEntryMut.RawOccupiedEntryMut { + ensureGuardNode$u16_u32(self.values); + const newNode: Node.Node = allocateNode$u16_u32(self.free); + Node.putEntry$u16_u32(newNode, [key, value] as [rt.u16, rt.u32]); + attachBefore$u16_u32(newNode, asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.values.v)); + const _18: rt.HashTable> = self.entry.t; + const _20: Node.Node = newNode; + const captured = hasher; + const node: rt.TableEntry> = rt.tableInsertUnique(_18, hash, _20, (k: Node.Node): rt.u64 => { + return captured(Node.keyRef$u16_u32(k)); + }); + return { hashBuilder: self.hashBuilder as rt.RandomState, free: self.free, values: self.values, entry: node }; +} + +export function insertEntryWithHasher$u16_u32_hash_RandomState_hashlink_linked_hash_map_RawVacantEntryMut_K_V_S_insert_hashed_nocheck_closure0_u16_u32_hash_RandomState(self: RawVacantEntryMut, hash: rt.u64, key: rt.u16, value: rt.u32, hasher: (a0: rt.u16) => rt.u64): RawOccupiedEntryMut.RawOccupiedEntryMut { + ensureGuardNode$u16_u32(self.values); + const newNode: Node.Node = allocateNode$u16_u32(self.free); + Node.putEntry$u16_u32(newNode, [key, value] as [rt.u16, rt.u32]); + attachBefore$u16_u32(newNode, asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.values.v)); + const _18: rt.HashTable> = self.entry.t; + const _20: Node.Node = newNode; + const captured = hasher; + const node: rt.TableEntry> = rt.tableInsertUnique(_18, hash, _20, (k: Node.Node): rt.u64 => { + return captured(Node.keyRef$u16_u32(k)); + }); + return { hashBuilder: self.hashBuilder as rt.RandomState, free: self.free, values: self.values, entry: node }; +} + +export function insertEntryHashedNocheck$u16_u32_hash_RandomState(self: RawVacantEntryMut, hash: rt.u64, key: rt.u16, value: rt.u32): RawOccupiedEntryMut.RawOccupiedEntryMut { + const hashBuilder: rt.RandomState = self.hashBuilder as rt.RandomState; + return insertEntryWithHasher$u16_u32_hash_RandomState_hashlink_linked_hash_map_RawVacantEntryMut_K_V_S_insert_entry_hashed_nocheck_closure0_u16_u32_hash_RandomState(self, hash, key, value, (k: rt.u16): rt.u64 => { + return hashKey$hash_RandomState_u16(hashBuilder, k); + }); +} + +export function insertEntry$u16_u32_hash_RandomState(self: RawVacantEntryMut, key: rt.u16, value: rt.u32): RawOccupiedEntryMut.RawOccupiedEntryMut { + return insertEntryHashedNocheck$u16_u32_hash_RandomState(self, hashKey$hash_RandomState_u16(self.hashBuilder as rt.RandomState, key), key, value); +} + +//# sourceMappingURL=linked_hash_map.RawVacantEntryMut.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.Remainder$drop_value_nodes.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.Remainder$drop_value_nodes.ts new file mode 100644 index 000000000..0eff3568e --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.Remainder$drop_value_nodes.ts @@ -0,0 +1,25 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import * as Node from "../hashlink/linked_hash_map.Node.ts"; + +export class Remainder { + cur: Node.Node; + guard: Node.Node; + + constructor(cur: Node.Node, guard: Node.Node) { + this.cur = cur; + this.guard = guard; + } +} + +export function drop$drop_value_nodes_ops_Drop_u16_u32(self: Remainder): void { + while (self.cur !== self.guard) { + const prev: Node.Node = self.cur.links.at4; + Node.takeEntry$u16_u32(self.cur); + self.cur; + self.cur = prev; + } +} + +//# sourceMappingURL=linked_hash_map.Remainder$drop_value_nodes.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.ValueLinks.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.ValueLinks.ts new file mode 100644 index 000000000..0d7bf7c1a --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.ValueLinks.ts @@ -0,0 +1,10 @@ +// Generated by rust2-ts. Do not edit. + +import type * as Node from "../hashlink/linked_hash_map.Node.ts"; + +export interface ValueLinks { + next: Node.Node; + prev: Node.Node; +} + +//# sourceMappingURL=linked_hash_map.ValueLinks.ts.map diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.ts new file mode 100644 index 000000000..ed7fbe772 --- /dev/null +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.ts @@ -0,0 +1,111 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { copy$mem_MaybeUninit_tuple2_u16_u32, drop$hashlink_linked_hash_map_drop_value_nodes_Remainder_u16_u32 as lib_drop$hashlink_linked_hash_map_drop_value_nodes_Remainder_u16_u32 } from "../lib.ts"; +import type * as Links from "../hashlink/linked_hash_map.Links.ts"; +import * as Node from "../hashlink/linked_hash_map.Node.ts"; +import * as Remainder$drop_value_nodes from "../hashlink/linked_hash_map.Remainder$drop_value_nodes.ts"; +import type * as ValueLinks from "../hashlink/linked_hash_map.ValueLinks.ts"; + +export function asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self: Node.Node | undefined): Node.Node { + if (self === undefined) { + return rt.NULL; + } + return self; +} + +export function ensureGuardNode$u16_u32(head: rt.Place | undefined>): void { + if (head.v === undefined) { + const _8: [rt.u16, rt.u32] = copy$mem_MaybeUninit_tuple2_u16_u32(([undefined, undefined] as unknown) as [rt.u16, rt.u32]); + const _10: ValueLinks.ValueLinks = { next: rt.DANGLING, prev: rt.DANGLING }; + const p: Node.Node = { entry: _8, links: ({ at0: _10.next, at4: _10.prev } as unknown) as Links.Links }; + const _13: ValueLinks.ValueLinks = { next: p, prev: p }; + const _16: Node.Node = p; + _16.links.at0 = _13.next; + _16.links.at4 = _13.prev; + head.v = p; + } +} + +export function attachBefore$u16_u32(toAttach: Node.Node, node: Node.Node): void { + const _3: ValueLinks.ValueLinks = { next: node, prev: node.links.at4 }; + const _8: Node.Node = toAttach; + _8.links.at0 = _3.next; + _8.links.at4 = _3.prev; + node.links.at4 = toAttach; + (toAttach.links.at4.links.at0 as Node.Node) = toAttach; +} + +export function detachNode$u16_u32(node: Node.Node): void { + (node.links.at4.links.at0 as Node.Node) = node.links.at0 as Node.Node; + (node.links.at0 as Node.Node).links.at4 = node.links.at4; +} + +export function pushFree$u16_u32(freeList: rt.Place | undefined>, node: Node.Node): void { + node.links.at0 = freeList.v; + freeList.v = node; +} + +export function popFree$u16_u32(freeList: rt.Place | undefined>): Node.Node | undefined { + if (freeList.v !== undefined) { + const free: Node.Node = freeList.v; + freeList.v = free.links.at0; + return free; + } + return undefined; +} + +export function allocateNode$u16_u32(freeList: rt.Place | undefined>): Node.Node { + const _2: Node.Node | undefined = popFree$u16_u32(freeList); + if (_2 !== undefined) { + const free: Node.Node = _2; + const _5: ValueLinks.ValueLinks = { next: rt.DANGLING, prev: rt.DANGLING }; + const _8: Node.Node = free; + _8.links.at0 = _5.next; + _8.links.at4 = _5.prev; + return free; + } + const _13: [rt.u16, rt.u32] = copy$mem_MaybeUninit_tuple2_u16_u32(([undefined, undefined] as unknown) as [rt.u16, rt.u32]); + const _15: ValueLinks.ValueLinks = { next: rt.DANGLING, prev: rt.DANGLING }; + return { entry: _13, links: ({ at0: _15.next, at4: _15.prev } as unknown) as Links.Links }; +} + +export function dropValueNodes$u16_u32(guard: Node.Node): void { + const cur: Node.Node = guard.links.at4; + const _5: ValueLinks.ValueLinks = { next: guard, prev: guard }; + const _8: Node.Node = guard; + _8.links.at0 = _5.next; + _8.links.at4 = _5.prev; + const rem: Remainder$drop_value_nodes.Remainder = new Remainder$drop_value_nodes.Remainder(cur, guard); + while (rem.cur !== guard) { + const prev: Node.Node = rem.cur.links.at4; + Node.takeEntry$u16_u32(rem.cur); + rem.cur = prev; + } + lib_drop$hashlink_linked_hash_map_drop_value_nodes_Remainder_u16_u32(rem); +} + +export function dropFreeNodes$u16_u32(free: Node.Node | undefined): void { + while (free !== undefined) { + const someFree: Node.Node = free; + const nextFree: Node.Node | undefined = someFree.links.at0; + someFree; + free = nextFree; + } +} + +export function removeNode$u16_u32(freeList: rt.Place | undefined>, node: Node.Node): [rt.u16, rt.u32] { + detachNode$u16_u32(node); + pushFree$u16_u32(freeList, node); + return Node.takeEntry$u16_u32(node); +} + +export function hashNode$hash_RandomState_u16_u32(s: rt.RandomState, node: Node.Node): rt.u64 { + return hashKey$hash_RandomState_u16(s, Node.keyRef$u16_u32(node)); +} + +export function hashKey$hash_RandomState_u16(s: rt.RandomState, k: rt.u16): rt.u64 { + return rt.hashOne(s, k, rt.hash16); +} + +//# sourceMappingURL=linked_hash_map.ts.map diff --git a/ts/fixtures/stock/expected/lib.ts b/ts/fixtures/stock/expected/lib.ts new file mode 100644 index 000000000..c283edb14 --- /dev/null +++ b/ts/fixtures/stock/expected/lib.ts @@ -0,0 +1,31 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import * as DropFilteredValues from "./hashlink/linked_hash_map.DropFilteredValues.ts"; +import * as LinkedHashMap from "./hashlink/linked_hash_map.LinkedHashMap.ts"; +import * as Remainder$drop_value_nodes from "./hashlink/linked_hash_map.Remainder$drop_value_nodes.ts"; + +export function copy$mem_MaybeUninit_tuple2_u16_u32(v: [rt.u16, rt.u32]): [rt.u16, rt.u32] { + if (v === undefined) { + return v; + } + return [v[0], v[1]]; +} + +export function copy$tuple2_u16_u32(v: [rt.u16, rt.u32]): [rt.u16, rt.u32] { + return [v[0], v[1]]; +} + +export function drop$hashlink_LinkedHashMap_u16_u32_hash_RandomState(_1: LinkedHashMap.LinkedHashMap): void { + LinkedHashMap.drop$ops_Drop_u16_u32_hash_RandomState(_1); +} + +export function drop$hashlink_linked_hash_map_DropFilteredValues_u16_u32(_1: DropFilteredValues.DropFilteredValues): void { + DropFilteredValues.drop$ops_Drop_u16_u32(_1); +} + +export function drop$hashlink_linked_hash_map_drop_value_nodes_Remainder_u16_u32(_1: Remainder$drop_value_nodes.Remainder): void { + Remainder$drop_value_nodes.drop$drop_value_nodes_ops_Drop_u16_u32(_1); +} + +//# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/stock/expected/maps.ts b/ts/fixtures/stock/expected/maps.ts new file mode 100644 index 000000000..ecaf2902d --- /dev/null +++ b/ts/fixtures/stock/expected/maps.ts @@ -0,0 +1,644 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { copy$tuple2_u16_u32, drop$hashlink_LinkedHashMap_u16_u32_hash_RandomState as lib_drop$hashlink_LinkedHashMap_u16_u32_hash_RandomState } from "./lib.ts"; +import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, mapOr$1787w44 as option_mapOr$1787w44, mapOr$0iaogpf as option_mapOr$0iaogpf, mapOr$1vcgixw as option_mapOr$1vcgixw } from "./core/option.ts"; +import * as LinkedHashMap from "./hashlink/linked_hash_map.LinkedHashMap.ts"; + +/** A length, capacity, or value, as four bytes. */ +export function put(out: rt.ByteVec, n: rt.usize): void { + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(rt.u32ToBeBytes(n as rt.u32).reverse())); +} + +/** + * `hashbrown::HashMap` and `HashSet` by its own hasher, of keys that are JS primitives + * and keys that are not, through each pair of bytes: a step and its key. + */ +export function map(data: rt.ByteSlice, out: rt.ByteVec): void { + const own: rt.HashMap = rt.hashNew() as rt.HashMap; + const pairs: rt.HashMap<[rt.u8, rt.u8], rt.u32> = rt.hashNew(rt.hashing(rt.hashTuple([rt.hash8, rt.hash8]), rt.eqTuple([rt.same, rt.same]))) as rt.HashMap<[rt.u8, rt.u8], rt.u32>; + const set: rt.HashSet = rt.hashWithCapacity(rt.usizeDiv(data.length, 4), 117440512) as rt.HashSet; + const iter: rt.Iter = rt.byteSliceChunks(data, 2); + while (true) { + const _12: rt.ByteSlice | rt.Done = iter.next(); + if (_12 === rt.DONE) { + break; + } + rt.idx(_12, 0); + const _18: [rt.u8, rt.u8] = [_12.data[_12.start], option_unwrapOr$0r2bmpr(rt.byteSliceGet(_12, 1), 0)]; + const op: rt.u8 = _18[0]; + const arg: rt.u8 = _18[1]; + const key: rt.u16 = rt.u8Rem(arg, 16) as rt.u16; + const pair: [rt.u8, rt.u8] = [rt.u8Rem(arg, 4), rt.u8Rem(arg, 3)]; + const _35: rt.u8 = rt.u8Rem(op, 10); + if (_35 === 0 || _35 === 1) { + put(out, option_mapOr$1vcgixw(rt.hashInsert(own, key, op as rt.u32), 256, (v9: rt.u32): rt.usize => { + return v9 as rt.usize; + })); + put(out, option_mapOr$1vcgixw(rt.hashInsert(pairs, pair, op as rt.u32), 256, (v10: rt.u32): rt.usize => { + return v10 as rt.usize; + })); + } else if (_35 === 2) { + put(out, option_mapOr$1vcgixw(rt.hashRemove(own, key), 256, (v7: rt.u32): rt.usize => { + return v7 as rt.usize; + })); + put(out, option_mapOr$1vcgixw(rt.hashRemove(pairs, pair), 256, (v8: rt.u32): rt.usize => { + return v8 as rt.usize; + })); + } else if (_35 === 3) { + rt.byteVecPush(out, rt.hashContains(own, key) ? 1 : 0); + put(out, option_mapOr$1vcgixw(rt.hashGet(pairs, pair), 256, (_29: rt.u32): rt.usize => { + return _29 as rt.usize; + })); + } else if (_35 === 4) { + const _67: rt.Place | undefined = rt.hashGetMut(own, key); + if (_67 !== undefined) { + _67.v = rt.u32Add(_67.v, 1); + } + const _73: rt.u32 = arg as rt.u32; + const _74: rt.Place = rt.entryOrInsertMut(rt.entry(pairs, pair), 7); + _74.v = rt.u32Add(_74.v, _73); + } else if (_35 === 5) { + rt.hashRetainMut(own, (k4: rt.u16, v5: rt.Place): boolean => { + return rt.u16Rem(rt.u16Add(k4, v5.v & 0xffff), 3) !== 0; + }); + rt.hashRetainMut(pairs, (k5: [rt.u8, rt.u8], _3: rt.Place): boolean => { + return k5[0] !== rt.u8Rem(arg, 4); + }); + } else if (_35 === 6) { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u16Display(key, f)]; + const _88: string = rt.fmtFormat([args2[0]]); + rt.byteVecPush(out, rt.hashAdd(set, _88) ? 1 : 0); + const part = rt.u16Add(key, 1); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.u16Display(part, f)]; + const _102: string = rt.fmtFormat([args4[0]]); + rt.byteVecPush(out, rt.hashContains(set, _102) ? 1 : 0); + } else if (_35 === 7) { + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.u16Display(key, f)]; + const _117: string = rt.fmtFormat([args6[0]]); + rt.byteVecPush(out, rt.hashDelete(set, _117) ? 1 : 0); + } else if (_35 === 8) { + put(out, option_mapOr$0iaogpf(rt.hashRemoveEntry(own, key), 256, (_26: [rt.u16, rt.u32]): rt.usize => { + return rt.usizeAdd(_26[0] as rt.usize, _26[1] as rt.usize); + })); + } else { + put(out, rt.iterSum(rt.iterMap(rt.hashDrain(own), (_2: [rt.u16, rt.u32]): rt.u32 => { + return rt.u32Add(_2[0] as rt.u32, _2[1]); + }), rt.u32Add, 0) as rt.usize); + } + put(out, rt.hashLen(own)); + put(out, rt.hashLen(pairs)); + put(out, rt.hashLen(set)); + } + const own2: [rt.u16, rt.u32][] = rt.iterToArray(rt.hashIter(own), "next", 8); + rt.sliceSort(own2, rt.cmpLt(rt.cmpTuple<[rt.u16, rt.u32], rt.Ordering | undefined>([rt.cmp, rt.cmp]))); + for (const _155 of own2) { + const k: rt.u16 = _155[0]; + const v: rt.u32 = _155[1]; + put(out, k as rt.usize); + put(out, v as rt.usize); + } + const pairs2: [[rt.u8, rt.u8], rt.u32][] = rt.iterToArray(rt.iterMap(rt.hashIter(pairs), (_210: [[rt.u8, rt.u8], rt.u32]): [[rt.u8, rt.u8], rt.u32] => { + return [_210[0], _210[1]] as [[rt.u8, rt.u8], rt.u32]; + }), "next", 8); + rt.sliceSort(pairs2, rt.cmpLt(rt.cmpTuple<[[rt.u8, rt.u8], rt.u32], rt.Ordering | undefined>([rt.cmpTuple<[rt.u8, rt.u8], rt.Ordering | undefined>([rt.cmp, rt.cmp]), rt.cmp]))); + for (const _174 of pairs2) { + const a: rt.u8 = _174[0][0]; + const b: rt.u8 = _174[0][1]; + const v2: rt.u32 = _174[1]; + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(Uint8Array.of(a, b))); + put(out, v2 as rt.usize); + } + const set2: string[] = rt.iterToArray(rt.hashKeys(set), "next", 12); + rt.sliceSort(set2, rt.cmpLt(rt.cmpStr)); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(rt.strsJoin(set2, ","))); +} + +/** + * The hash of `key` that `kind` picks: one past 2^53 that sets apart a few keys, the + * same for every key, or each key's own. + */ +export function hash(kind: rt.u8, key: rt.u16): rt.u64 { + const _3: rt.u8 = rt.u8Rem(kind, 3); + if (_3 === 0) { + return rt.u64WrappingMul(rt.u16Rem(key, 5) as rt.u64, { hi: 0x9e3779b9, lo: 0x7f4a7c15 } as rt.u64); + } + if (_3 === 1) { + return 7; + } + return key as rt.u64; +} + +/** + * `hashbrown::HashTable` of `(key, value)`, found by the hash `data`'s first byte picks + * and the key's equality, through each pair of bytes after it: a step and its key. + */ +export function table(dataData: Uint8Array, dataStart: number, dataLen: number, out: rt.ByteVec): void { + if (dataLen !== 0) { + const kind: rt.u8 = dataData[dataStart]; + let t: rt.HashTable<[rt.u16, rt.u32]> = rt.hashNew() as rt.HashTable<[rt.u16, rt.u32]>; + const rehash: (a0: [rt.u16, rt.u32]) => rt.u64 = (e7: [rt.u16, rt.u32]): rt.u64 => { + return hash(kind, e7[0]); + }; + const n: rt.HashTable = rt.hashWithCapacity(rt.u8Rem(kind, 8) as rt.usize, 234881024) as rt.HashTable; + const iter: rt.Iter = rt.byteSliceChunks(new rt.ByteSlice(dataData, dataStart + 1, dataLen - 1), 2); + while (true) { + const _17: rt.ByteSlice | rt.Done = iter.next(); + if (_17 === rt.DONE) { + break; + } + rt.idx(_17, 0); + const _23: [rt.u8, rt.u8] = [_17.data[_17.start], option_unwrapOr$0r2bmpr(rt.byteSliceGet(_17, 1), 0)]; + const op: rt.u8 = _23[0]; + const arg: rt.u8 = _23[1]; + const key: rt.u16 = rt.u8Rem(arg, 16) as rt.u16; + const h: rt.u64 = hash(kind, key); + numbers(n, kind, op, key, out); + const _38: rt.u8 = rt.u8Rem(op, 9); + if (_38 === 0 || _38 === 1) { + const _40: rt.Result, rt.TableEntry<[rt.u16, rt.u32]>> = rt.tableFindEntry(t, h, (e18: [rt.u16, rt.u32]): boolean => { + return e18[0] === key; + }); + if (_40.ok) { + const e: rt.TableEntry<[rt.u16, rt.u32]> = _40.value; + const _46: rt.u32 = op as rt.u32; + const _47: [rt.u16, rt.u32] = e.v; + _47[1] = rt.u32Add(_47[1], _46); + put(out, e.v[1] as rt.usize); + } else { + put(out, rt.tableInsertUnique(_40.error.t, h, [key, op as rt.u32] as [rt.u16, rt.u32], rehash).v[1] as rt.usize); + } + } else if (_38 === 2) { + const _65: rt.Result, rt.TableEntry<[rt.u16, rt.u32]>> = rt.tableFindEntry(t, h, (e17: [rt.u16, rt.u32]): boolean => { + return e17[0] === key; + }); + if (_65.ok) { + const _74: [[rt.u16, rt.u32], rt.TableEntry<[rt.u16, rt.u32]>] = rt.tableRemove(_65.value); + const k: rt.u16 = _74[0][0]; + const v: rt.u32 = _74[0][1]; + const vacant: rt.TableEntry<[rt.u16, rt.u32]> = _74[1]; + put(out, rt.usizeAdd(k as rt.usize, v as rt.usize)); + put(out, rt.hashLen(vacant.t)); + } else { + put(out, 256); + } + } else if (_38 === 3) { + put(out, option_mapOr$0iaogpf(rt.tableFind(t, h, (e15: [rt.u16, rt.u32]): boolean => { + return e15[0] === key; + }), 256, (e16: [rt.u16, rt.u32]): rt.usize => { + return e16[1] as rt.usize; + })); + } else if (_38 === 4) { + const _89: [rt.u16, rt.u32] | undefined = rt.tableFind(t, h, (e14: [rt.u16, rt.u32]): boolean => { + return e14[0] === key; + }); + if (_89 !== undefined) { + _89[1] = rt.u32Mul(_89[1], 2); + } + } else if (_38 === 5) { + const _96: rt.TableEntry<[rt.u16, rt.u32]> = rt.tableEntry(t, h, (e13: [rt.u16, rt.u32]): boolean => { + return e13[0] === key; + }, rehash); + if (rt.tableOccupied(_96)) { + put(out, _96.v[1] as rt.usize); + } else { + put(out, rt.tableEntryInsert(_96, [key, 1] as [rt.u16, rt.u32]).v[1] as rt.usize); + } + } else if (_38 === 6) { + put(out, rt.tableOrInsert(rt.tableEntry(t, h, (e12: [rt.u16, rt.u32]): boolean => { + return e12[0] === key; + }, rehash), [key, 3] as [rt.u16, rt.u32]).v[1] as rt.usize); + } else if (_38 === 7) { + rt.tableRetain(t, (e11: [rt.u16, rt.u32]): boolean => { + e11[1] = rt.u32Add(e11[1], 1); + return rt.u16Rem(e11[0], 3) !== rt.u8Rem(arg, 3) as rt.u16; + }); + } else { + put(out, rt.iterSum(rt.iterMap(rt.hashValues(t), (e8: [rt.u16, rt.u32]): rt.u32 => { + return rt.u32Add(e8[0] as rt.u32, e8[1]); + }), rt.u32Add, 0) as rt.usize); + const copy: rt.HashTable<[rt.u16, rt.u32]> = rt.hashClone(t, undefined, copy$tuple2_u16_u32); + const drained: [rt.u16, rt.u32][] = rt.iterToArray(rt.tableDrain(t), "next", 8); + rt.sliceSort(drained, rt.cmpLt(rt.cmpTuple<[rt.u16, rt.u32], rt.Ordering | undefined>([rt.cmp, rt.cmp]))); + for (const _142 of drained) { + const k2: rt.u16 = _142[0]; + const v2: rt.u32 = _142[1]; + put(out, k2 as rt.usize); + put(out, v2 as rt.usize); + } + put(out, option_mapOr$0iaogpf(rt.tableFind(copy, h, (e9: [rt.u16, rt.u32]): boolean => { + return e9[0] === key; + }), 256, (e10: [rt.u16, rt.u32]): rt.usize => { + return e10[1] as rt.usize; + })); + put(out, rt.hashLen(copy)); + if ((arg & 1) === 1) { + t = copy; + } + } + put(out, rt.hashLen(t)); + rt.byteVecPush(out, rt.hashLen(t) === 0 ? 1 : 0); + } + const left: [rt.u16, rt.u32][] = rt.iterToArray(rt.hashValues(t), "next", 8); + rt.sliceSort(left, rt.cmpLt(rt.cmpTuple<[rt.u16, rt.u32], rt.Ordering | undefined>([rt.cmp, rt.cmp]))); + for (const _179 of left) { + const k3: rt.u16 = _179[0]; + const v3: rt.u32 = _179[1]; + put(out, k3 as rt.usize); + put(out, v3 as rt.usize); + } + } +} + +/** + * `HashTable`, whose items JS copies, so a `&mut` to one is its place: each item's + * key in its high half and a count in its low half, changed by step `op` of `key`. + */ +export function numbers(n: rt.HashTable, kind: rt.u8, op: rt.u8, key: rt.u16, out: rt.ByteVec): void { + const h: rt.u64 = hash(kind, key); + const ours: (a0: rt.u32) => boolean = (e5: rt.u32): boolean => { + return (e5 >>> 16) === key as rt.u32; + }; + const rehash: (a0: rt.u32) => rt.u64 = (e6: rt.u32): rt.u64 => { + return hash(kind, (e6 >>> 16) & 0xffff); + }; + b0: { + const _12: rt.u8 = rt.u8Rem(op, 6); + if (_12 === 0) { + put(out, rt.tableOrInsert(rt.tableAndModifyMut(rt.tableEntry(n, h, ours, rehash), (e8: rt.Place): undefined => { + e8.v = rt.u32Add(e8.v, 1); + }), (((key as rt.u32)) << 16) >>> 0).v as rt.usize); + } else if (_12 === 1) { + const _26: rt.Place | undefined = rt.tableFindMut(n, h, ours); + if (_26 === undefined) { + put(out, rt.tableInsertUnique(n, h, (((key as rt.u32)) << 16) >>> 0, rehash).v as rt.usize); + } else { + _26.v = rt.u32Add(_26.v, 2); + } + } else if (_12 === 2) { + const _38: rt.Result, rt.TableEntry> = rt.tableFindEntry(n, h, ours); + if (_38.ok) { + const e2: rt.TableEntry = _38.value; + const _41: rt.Place = e2; + _41.v = rt.u32Add(_41.v, 3); + const e3: rt.Place = e2; + e3.v = rt.u32Add(e3.v, 4); + } + } else if (_12 === 3) { + rt.tableRetainMut(n, (e7: rt.Place): boolean => { + e7.v = rt.u32Add(e7.v, 5); + return ((e7.v & 255) >>> 0) < 200; + }); + } else { + if (_12 === 4) { + const iter: rt.Iter> = rt.hashValuesMut(n); + while (true) { + const _50: rt.Place | rt.Done = iter.next(); + if (_50 === rt.DONE) { + break; + } + _50.v = (_50.v ^ 1) >>> 0; + } + const iter2: rt.Iter> = rt.hashValuesMut(n); + while (true) { + const _56: rt.Place | rt.Done = iter2.next(); + if (_56 === rt.DONE) { + break b0; + } + _56.v = rt.u32Add(_56.v, 1); + } + } + const _61: rt.TableEntry = rt.tableEntry(n, h, ours, rehash); + if (rt.tableOccupied(_61)) { + const _64: rt.Place = _61; + _64.v = rt.u32Add(_64.v, 6); + } + } + } + const items: rt.u32[] = rt.iterToArray(rt.hashValues(n), "next", 4); + rt.sliceSort(items, rt.lt); + for (const _77 of items) { + put(out, _77 as rt.usize); + } +} + +/** + * What std's guarantees fix of a hash collection's capacity, `cap`, holding `len`: at + * least `len` and `floor`, the most an operation just asked for. + */ +export function holds(out: rt.ByteVec, len: rt.usize, cap: rt.usize, floor: rt.usize): void { + put(out, len); + rt.byteVecPush(out, cap >= rt.max(len, floor) ? 1 : 0); +} + +/** + * The capacity of `hashbrown::HashMap`, `HashSet`, and `HashTable`, which follows std's + * guarantees only, as each step of `data` grows, shrinks, or remakes them: at least + * their length and what `with_capacity` and `reserve` asked for, no less past `shrink_to` + * than it asked for of what there was, doubled as inserts grow it, none until one + * allocates, and an allocation while it holds any. + */ +export function capacity(dataData: Uint8Array, dataStart: number, dataLen: number, out: rt.ByteVec): void { + let _14!: [rt.HashMap, rt.HashSet, rt.HashTable]; + let floor!: rt.usize; + let _53!: boolean; + let _164!: boolean; + let _206!: rt.usize; + let floor2!: rt.usize; + let grew!: boolean; + let _242!: boolean; + let _259!: boolean; + if (dataLen !== 0) { + const first: rt.u8 = dataData[dataStart]; + const made: rt.usize = first >> 2 as rt.usize; + if ((first & 3) === 0) { + _14 = [rt.hashNew() as rt.HashMap, rt.hashNew() as rt.HashSet, rt.hashNew() as rt.HashTable] as [rt.HashMap, rt.HashSet, rt.HashTable]; + } else { + rt.randomState(); + _14 = [rt.hashWithCapacity(made, 58720256) as rt.HashMap, rt.hashWithCapacity(made, 469762048) as rt.HashSet, rt.hashWithCapacity(made, 234881024) as rt.HashTable] as [rt.HashMap, rt.HashSet, rt.HashTable]; + } + const m: rt.HashMap = _14[0]; + const s: rt.HashSet = _14[1]; + const t: rt.HashTable = _14[2]; + if (!((first & 3) === 0)) { + floor = made; + } else { + floor = 0; + } + const iter: rt.Iter<[rt.usize, rt.usize]> = rt.iterSlice([[rt.hashCapacity(m), rt.hashLen(m)] as [rt.usize, rt.usize], [rt.hashCapacity(s), rt.hashLen(s)] as [rt.usize, rt.usize], [rt.hashCapacity(t), rt.hashLen(t)] as [rt.usize, rt.usize]]); + while (true) { + const _44: [rt.usize, rt.usize] | rt.Done = iter.next(); + if (_44 === rt.DONE) { + break; + } + const cap: rt.usize = _44[0]; + holds(out, _44[1], cap, floor); + if (!(cap === 0)) { + _53 = (first & 3) !== 0; + } else { + _53 = true; + } + rt.byteVecPush(out, _53 ? 1 : 0); + } + const hasher: rt.RandomState = rt.randomState(); + const rehash: (a0: rt.u32) => rt.u64 = (v3: rt.u32): rt.u64 => { + return rt.hashOne(hasher, v3, rt.hash32); + }; + let kept: boolean[] = Array.from({ length: 3 }, (): boolean => { + return true; + }); + const iter2: rt.Iter = rt.byteSliceChunks(new rt.ByteSlice(dataData, dataStart + 1, dataLen - 1), 2); + l7: while (true) { + const _63: rt.ByteSlice | rt.Done = iter2.next(); + if (_63 === rt.DONE) { + break; + } + rt.idx(_63, 0); + const _69: [rt.u8, rt.u8] = [_63.data[_63.start], option_unwrapOr$0r2bmpr(rt.byteSliceGet(_63, 1), 0)]; + const op: rt.u8 = _69[0]; + const arg: rt.u8 = _69[1]; + const before: rt.usize[] = [rt.hashCapacity(m), rt.hashCapacity(s), rt.hashCapacity(t)]; + let additional: rt.usize | undefined = undefined; + let keptMin: rt.usize | undefined = undefined; + b8: { + const _89: rt.u8 = rt.u8Rem(op, 7); + if (0 <= _89) { + if (_89 <= 2) { + rt.hashInsert(m, arg as rt.u16, op as rt.u64); + rt.hashAdd(s, arg); + const v: rt.u32 = arg as rt.u32; + const _104: rt.u64 = rt.hashOne(hasher, v, rt.hash32); + if (!((rt.tableFind(t, _104, (_2: rt.u32): boolean => { + return _2 === v; + }) === undefined))) { + break b8; + } + const _110: rt.u64 = rt.hashOne(hasher, v, rt.hash32); + rt.tableInsertUnique(t, _110, v, rehash); + break b8; + } + } + if (_89 === 3) { + rt.hashRemove(m, arg as rt.u16); + rt.hashDelete(s, arg); + const v2: rt.u32 = arg as rt.u32; + const _122: rt.u64 = rt.hashOne(hasher, v2, rt.hash32); + const _120: rt.Result, rt.TableEntry> = rt.tableFindEntry(t, _122, (_23: rt.u32): boolean => { + return _23 === v2; + }); + if (_120.ok) { + rt.tableRemove(_120.value); + } + kept = Array.from({ length: 3 }, (): boolean => { + return false; + }); + } else if (_89 === 4) { + const n: rt.usize = rt.u8Rem(arg, 40) as rt.usize; + rt.hashReserve(m, n, 58720256); + rt.hashReserve(s, n, 469762048); + rt.hashReserve(t, n, 234881024, rehash); + additional = n; + } else if (_89 === 5) { + const min: rt.usize = rt.u8Rem(arg, 20) as rt.usize; + if ((arg & 128) === 0) { + rt.hashShrinkTo(m, min); + rt.hashShrinkTo(s, min); + rt.hashShrinkTo(t, min, rehash); + keptMin = min; + } else { + rt.hashShrinkTo(m, 0); + rt.hashShrinkTo(s, 0); + rt.hashShrinkTo(t, 0, rehash); + } + kept = Array.from({ length: 3 }, (): boolean => { + return true; + }); + } else { + rt.hashClear(m); + rt.hashClear(s); + rt.hashClear(t, undefined); + b10: { + if (rt.hashCapacity(m) >= before[0]) { + if (rt.hashCapacity(s) >= before[1]) { + _164 = rt.hashCapacity(t) >= before[2]; + break b10; + } + } + _164 = false; + } + rt.byteVecPush(out, _164 ? 1 : 0); + } + } + const lens: rt.usize[] = [rt.hashLen(m), rt.hashLen(s), rt.hashLen(t)]; + const caps: rt.usize[] = [rt.hashCapacity(m), rt.hashCapacity(s), rt.hashCapacity(t)]; + const sizes: rt.usize[] = [rt.hashAllocationSize(m, 16), rt.hashAllocationSize(s, 1), rt.hashAllocationSize(t, 4)]; + let iterStart: rt.usize = 0; + const iterEnd: rt.usize = 3; + while (true) { + if (!((_206 = iterStart++) < iterEnd)) { + continue l7; + } + const i: rt.usize = _206; + const _211: [rt.usize | undefined, rt.usize | undefined] = [additional, keptMin]; + if (_211[0] !== undefined) { + const n2: rt.usize = _211[0]; + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + floor2 = rt.usizeAdd(lens[i], n2); + } else if (_211[1] !== undefined) { + const min2: rt.usize = _211[1]; + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + const _221: rt.usize = before[i]; + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + floor2 = rt.min(_221, rt.max(lens[i], min2)); + } else { + floor2 = 0; + } + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + const _227: rt.usize = lens[i]; + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + holds(out, _227, caps[i], floor2); + if (!(rt.u8Rem(op, 7) <= 2)) { + grew = false; + } else { + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + const _236: rt.usize = caps[i]; + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + grew = _236 > before[i]; + } + b17: { + if (grew) { + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + if (kept[i]) { + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + if (!(before[i] === 0)) { + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + const _249: rt.usize = caps[i]; + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + _242 = _249 >= rt.usizeMul(2, before[i]); + break b17; + } + } + } + _242 = true; + } + rt.byteVecPush(out, _242 ? 1 : 0); + if (grew) { + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + kept[i] = true; + } + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + if (!(lens[i] === 0)) { + if (!(i < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + i); + } + _259 = sizes[i] > 0; + } else { + _259 = true; + } + rt.byteVecPush(out, _259 ? 1 : 0); + } + } + } +} + +/** + * `hashlink::LinkedHashMap`, by std's hasher, whose order is its own: each step of + * `data` and then every entry, from the front. + */ +export function linked(data: rt.ByteSlice, out: rt.ByteVec): void { + const m: LinkedHashMap.LinkedHashMap = LinkedHashMap.withHasher$u16_u32_hash_RandomState(rt.randomState()); + const iter: rt.Iter = rt.byteSliceChunks(data, 2); + while (true) { + const _8: rt.ByteSlice | rt.Done = iter.next(); + if (_8 === rt.DONE) { + break; + } + rt.idx(_8, 0); + const _14: [rt.u8, rt.u8] = [_8.data[_8.start], option_unwrapOr$0r2bmpr(rt.byteSliceGet(_8, 1), 0)]; + const op: rt.u8 = _14[0]; + const arg: rt.u8 = _14[1]; + const key: rt.u16 = rt.u8Rem(arg, 12) as rt.u16; + const _26: rt.u8 = rt.u8Rem(op, 8); + if (_26 === 0 || _26 === 1) { + put(out, option_mapOr$1vcgixw(LinkedHashMap.insert$u16_u32_hash_RandomState(m, key, op as rt.u32), 256, (v8: rt.u32): rt.usize => { + return v8 as rt.usize; + })); + } else if (_26 === 2) { + put(out, option_mapOr$1vcgixw(LinkedHashMap.remove$u16_u32_hash_RandomState_u16(m, key), 256, (v7: rt.u32): rt.usize => { + return v7 as rt.usize; + })); + } else if (_26 === 3) { + put(out, option_mapOr$1787w44(LinkedHashMap.toBack$u16_u32_hash_RandomState_u16(m, key), 256, (v6: rt.Place): rt.usize => { + return v6.v as rt.usize; + })); + } else if (_26 === 4) { + if ((arg & 1) === 0) { + put(out, option_mapOr$0iaogpf(LinkedHashMap.popFront$u16_u32_hash_RandomState(m), 256, (_210: [rt.u16, rt.u32]): rt.usize => { + return rt.usizeAdd(rt.usizeMul(_210[0] as rt.usize, 4096), _210[1] as rt.usize); + })); + } else { + put(out, option_mapOr$0iaogpf(LinkedHashMap.popBack$u16_u32_hash_RandomState(m), 256, (_25: [rt.u16, rt.u32]): rt.usize => { + return rt.usizeAdd(rt.usizeMul(_25[0] as rt.usize, 4096), _25[1] as rt.usize); + })); + } + } else if (_26 === 5) { + put(out, option_mapOr$0iaogpf(LinkedHashMap.front$u16_u32_hash_RandomState(m), 256, (_2: [rt.u16, rt.u32]): rt.usize => { + return _2[0] as rt.usize; + })); + put(out, option_mapOr$0iaogpf(LinkedHashMap.back$u16_u32_hash_RandomState(m), 256, (_22: [rt.u16, rt.u32]): rt.usize => { + return _22[0] as rt.usize; + })); + } else if (_26 === 6) { + put(out, option_mapOr$1vcgixw(LinkedHashMap.insertFront$u16_u32_hash_RandomState(m, key, op as rt.u32), 256, (v3: rt.u32): rt.usize => { + return v3 as rt.usize; + })); + } else { + LinkedHashMap.retain$u16_u32_hash_RandomState_maps_linked_closure8(m, (k2: rt.u16, v2: rt.Place): boolean => { + v2.v = rt.u32Add(v2.v, 1); + return rt.u16Rem(k2, 3) !== rt.u8Rem(arg, 3) as rt.u16; + }); + } + put(out, LinkedHashMap.len$u16_u32_hash_RandomState(m)); + rt.byteVecPush(out, LinkedHashMap.capacity$u16_u32_hash_RandomState(m) >= LinkedHashMap.len$u16_u32_hash_RandomState(m) ? 1 : 0); + } + while (true) { + const _72: [rt.u16, rt.u32] | undefined = LinkedHashMap.popFront$u16_u32_hash_RandomState(m); + if (_72 !== undefined) { + const k: rt.u16 = _72[0]; + const v: rt.u32 = _72[1]; + put(out, k as rt.usize); + put(out, v as rt.usize); + } else { + break; + } + } + lib_drop$hashlink_LinkedHashMap_u16_u32_hash_RandomState(m); +} + +//# sourceMappingURL=maps.ts.map diff --git a/ts/fixtures/stock/src/harness.rs b/ts/fixtures/stock/src/harness.rs index 2f0abc6ca..441621070 100644 --- a/ts/fixtures/stock/src/harness.rs +++ b/ts/fixtures/stock/src/harness.rs @@ -20,6 +20,10 @@ fn cases() -> Vec<(&'static str, Case)> { ("buf::writes", crate::buf::writes), ("buf::edits", crate::buf::edits), ("buf::reserves", crate::buf::reserves), + ("maps::map", crate::maps::map), + ("maps::table", crate::maps::table), + ("maps::capacity", crate::maps::capacity), + ("maps::linked", crate::maps::linked), ] } diff --git a/ts/fixtures/stock/src/lib.rs b/ts/fixtures/stock/src/lib.rs index d06747c6d..021ce541f 100644 --- a/ts/fixtures/stock/src/lib.rs +++ b/ts/fixtures/stock/src/lib.rs @@ -1,10 +1,12 @@ -//! Calls into `bytes`, `rand`, and `tracing`, which translation maps itself, so this -//! config binds and leaves out nothing. +//! Calls into `bytes`, `hashbrown`, `rand`, and `tracing`, which translation maps itself, +//! and into `hashlink`, which translates over `hashbrown`, so this config binds and leaves +//! out nothing. //! //! `harness::run` is the entry point both sides of the differential test run. mod buf; pub mod harness; +mod maps; #[cfg(target_arch = "wasm32")] #[path = "../../wasm.rs"] mod wasm; diff --git a/ts/fixtures/stock/src/maps.rs b/ts/fixtures/stock/src/maps.rs new file mode 100644 index 000000000..4a16a2914 --- /dev/null +++ b/ts/fixtures/stock/src/maps.rs @@ -0,0 +1,386 @@ +//! `hashbrown`'s tables, which translation maps to the runtime's map as it maps std's, and +//! `hashlink`'s, which translate from their source over a `HashTable`. A hash table's +//! iteration order is unspecified, so only what does not depend on it is written, but a +//! linked map's order is its own, and written as it is. + +use std::collections::hash_map::RandomState; +use std::hash::BuildHasher; + +use hashbrown::hash_table::{Entry, HashTable}; +use hashbrown::{HashMap, HashSet}; +use hashlink::LinkedHashMap; + +/// A length, capacity, or value, as four bytes. +fn put(out: &mut Vec, n: usize) { + out.extend_from_slice(&(n as u32).to_le_bytes()); +} + +/// `hashbrown::HashMap` and `HashSet` by its own hasher, of keys that are JS primitives +/// and keys that are not, through each pair of bytes: a step and its key. +pub fn map(data: &[u8], out: &mut Vec) { + let mut own: HashMap = HashMap::new(); + let mut pairs: HashMap<(u8, u8), u32> = HashMap::default(); + let mut set: HashSet = HashSet::with_capacity(data.len() / 4); + for step in data.chunks(2) { + let (op, arg) = (step[0], step.get(1).copied().unwrap_or(0)); + let key = u16::from(arg % 16); + let pair = (arg % 4, arg % 3); + match op % 10 { + 0 | 1 => { + let old = own.insert(key, u32::from(op)); + put(out, old.map_or(0x100, |v| v as usize)); + let old = pairs.insert(pair, u32::from(op)); + put(out, old.map_or(0x100, |v| v as usize)); + } + 2 => { + put(out, own.remove(&key).map_or(0x100, |v| v as usize)); + put(out, pairs.remove(&pair).map_or(0x100, |v| v as usize)); + } + 3 => { + out.push(own.contains_key(&key) as u8); + put(out, pairs.get(&pair).map_or(0x100, |&v| v as usize)); + } + 4 => { + if let Some(v) = own.get_mut(&key) { + *v += 1; + } + *pairs.entry(pair).or_insert(7) += u32::from(arg); + } + 5 => { + own.retain(|k, v| (*k + *v as u16) % 3 != 0); + pairs.retain(|k, _| k.0 != arg % 4); + } + 6 => { + out.push(set.insert(format!("{key}")) as u8); + out.push(set.contains(&format!("{}", key + 1)) as u8); + } + 7 => out.push(set.remove(&format!("{key}")) as u8), + 8 => { + let removed = own.remove_entry(&key); + put(out, removed.map_or(0x100, |(k, v)| usize::from(k) + v as usize)); + } + _ => { + let drained: u32 = own.drain().map(|(k, v)| u32::from(k) + v).sum(); + put(out, drained as usize); + } + } + put(out, own.len()); + put(out, pairs.len()); + put(out, set.len()); + } + let mut own: Vec<(u16, u32)> = own.into_iter().collect(); + own.sort(); + for (k, v) in own { + put(out, usize::from(k)); + put(out, v as usize); + } + let mut pairs: Vec<((u8, u8), u32)> = pairs.iter().map(|(k, v)| (*k, *v)).collect(); + pairs.sort(); + for ((a, b), v) in pairs { + out.extend_from_slice(&[a, b]); + put(out, v as usize); + } + let mut set: Vec = set.into_iter().collect(); + set.sort(); + out.extend_from_slice(set.join(",").as_bytes()); +} + +/// The hash of `key` that `kind` picks: one past 2^53 that sets apart a few keys, the +/// same for every key, or each key's own. +fn hash(kind: u8, key: u16) -> u64 { + match kind % 3 { + 0 => u64::from(key % 5).wrapping_mul(0x9e37_79b9_7f4a_7c15), + 1 => 7, + _ => u64::from(key), + } +} + +/// `hashbrown::HashTable` of `(key, value)`, found by the hash `data`'s first byte picks +/// and the key's equality, through each pair of bytes after it: a step and its key. +pub fn table(data: &[u8], out: &mut Vec) { + let Some((&kind, steps)) = data.split_first() else { + return; + }; + let mut t: HashTable<(u16, u32)> = HashTable::new(); + let rehash = |e: &(u16, u32)| hash(kind, e.0); + let mut n: HashTable = HashTable::with_capacity(usize::from(kind % 8)); + for step in steps.chunks(2) { + let (op, arg) = (step[0], step.get(1).copied().unwrap_or(0)); + let key = u16::from(arg % 16); + let h = hash(kind, key); + numbers(&mut n, kind, op, key, out); + match op % 9 { + 0 | 1 => match t.find_entry(h, |e| e.0 == key) { + Ok(mut e) => { + e.get_mut().1 += u32::from(op); + put(out, e.get().1 as usize); + } + Err(absent) => { + let e = absent.into_table().insert_unique(h, (key, u32::from(op)), rehash); + put(out, e.get().1 as usize); + } + }, + 2 => match t.find_entry(h, |e| e.0 == key) { + Ok(e) => { + let ((k, v), vacant) = e.remove(); + put(out, usize::from(k) + v as usize); + put(out, vacant.into_table().len()); + } + Err(_) => put(out, 0x100), + }, + 3 => put(out, t.find(h, |e| e.0 == key).map_or(0x100, |e| e.1 as usize)), + 4 => { + if let Some(e) = t.find_mut(h, |e| e.0 == key) { + e.1 *= 2; + } + } + 5 => match t.entry(h, |e| e.0 == key, rehash) { + Entry::Occupied(e) => put(out, e.into_mut().1 as usize), + Entry::Vacant(e) => put(out, e.insert((key, 1)).into_mut().1 as usize), + }, + 6 => { + let e = t.entry(h, |e| e.0 == key, rehash).or_insert((key, 3)); + put(out, e.get().1 as usize); + } + 7 => t.retain(|e| { + e.1 += 1; + e.0 % 3 != u16::from(arg % 3) + }), + _ => { + let sum: u32 = t.iter().map(|e| u32::from(e.0) + e.1).sum(); + put(out, sum as usize); + let copy = t.clone(); + let mut drained: Vec<(u16, u32)> = t.drain().collect(); + drained.sort(); + for (k, v) in drained { + put(out, usize::from(k)); + put(out, v as usize); + } + // The clone finds each item under the hash its original was given. + put(out, copy.find(h, |e| e.0 == key).map_or(0x100, |e| e.1 as usize)); + put(out, copy.len()); + if arg & 1 == 1 { + t = copy; + } + } + } + put(out, t.len()); + out.push(t.is_empty() as u8); + } + let mut left: Vec<(u16, u32)> = t.into_iter().collect(); + left.sort(); + for (k, v) in left { + put(out, usize::from(k)); + put(out, v as usize); + } +} + +/// `HashTable`, whose items JS copies, so a `&mut` to one is its place: each item's +/// key in its high half and a count in its low half, changed by step `op` of `key`. +fn numbers(n: &mut HashTable, kind: u8, op: u8, key: u16, out: &mut Vec) { + let h = hash(kind, key); + let ours = |e: &u32| *e >> 16 == u32::from(key); + let rehash = |e: &u32| hash(kind, (*e >> 16) as u16); + match op % 6 { + 0 => { + let e = n.entry(h, ours, rehash).and_modify(|e| *e += 1); + put(out, *e.or_insert(u32::from(key) << 16).get() as usize); + } + 1 => match n.find_mut(h, ours) { + Some(e) => *e += 2, + None => put( + out, + *n.insert_unique(h, u32::from(key) << 16, rehash).into_mut() as usize, + ), + }, + 2 => { + if let Ok(mut e) = n.find_entry(h, ours) { + *e.get_mut() += 3; + let e = e.into_mut(); + *e += 4; + } + } + 3 => n.retain(|e| { + *e += 5; + *e & 0xff < 200 + }), + 4 => { + for e in n.iter_mut() { + *e ^= 1; + } + for e in &mut *n { + *e += 1; + } + } + _ => { + if let Entry::Occupied(mut e) = n.entry(h, ours, rehash) { + *e.get_mut() += 6; + } + } + } + let mut items: Vec = n.iter().copied().collect(); + items.sort(); + for item in items { + put(out, item as usize); + } +} + +/// What std's guarantees fix of a hash collection's capacity, `cap`, holding `len`: at +/// least `len` and `floor`, the most an operation just asked for. +fn holds(out: &mut Vec, len: usize, cap: usize, floor: usize) { + put(out, len); + out.push(u8::from(cap >= len.max(floor))); +} + +/// The capacity of `hashbrown::HashMap`, `HashSet`, and `HashTable`, which follows std's +/// guarantees only, as each step of `data` grows, shrinks, or remakes them: at least +/// their length and what `with_capacity` and `reserve` asked for, no less past `shrink_to` +/// than it asked for of what there was, doubled as inserts grow it, none until one +/// allocates, and an allocation while it holds any. +pub fn capacity(data: &[u8], out: &mut Vec) { + let Some((&first, steps)) = data.split_first() else { + return; + }; + let made = usize::from(first >> 2); + let (mut m, mut s, mut t): (HashMap, HashSet, HashTable) = match first & 3 { + 0 => (HashMap::default(), HashSet::new(), HashTable::new()), + _ => ( + HashMap::with_capacity_and_hasher(made, RandomState::new()), + HashSet::with_capacity(made), + HashTable::with_capacity(made), + ), + }; + let floor = if first & 3 == 0 { 0 } else { made }; + for (cap, len) in [ + (m.capacity(), m.len()), + (s.capacity(), s.len()), + (t.capacity(), t.len()), + ] { + holds(out, len, cap, floor); + out.push(u8::from(cap == 0 || first & 3 != 0)); + } + let hasher = RandomState::new(); + let rehash = |v: &u32| hasher.hash_one(v); + // Whether nothing was removed since each last grew, so an insert that grows it doubles it. + let mut kept = [true; 3]; + for step in steps.chunks(2) { + let (op, arg) = (step[0], step.get(1).copied().unwrap_or(0)); + let before = [m.capacity(), s.capacity(), t.capacity()]; + // What `reserve` asked for past the length, or what `shrink_to` kept. + let (mut additional, mut kept_min) = (None, None); + match op % 7 { + 0..=2 => { + m.insert(u16::from(arg), u64::from(op)); + s.insert(arg); + let v = u32::from(arg); + if t.find(hasher.hash_one(v), |&o| o == v).is_none() { + t.insert_unique(hasher.hash_one(v), v, rehash); + } + } + 3 => { + m.remove(&u16::from(arg)); + s.remove(&arg); + let v = u32::from(arg); + if let Ok(e) = t.find_entry(hasher.hash_one(v), |&o| o == v) { + e.remove(); + } + kept = [false; 3]; + } + 4 => { + let n = usize::from(arg % 40); + m.reserve(n); + s.reserve(n); + t.reserve(n, rehash); + additional = Some(n); + } + 5 => { + let min = usize::from(arg % 20); + match arg & 0x80 { + 0 => { + m.shrink_to(min); + s.shrink_to(min); + t.shrink_to(min, rehash); + kept_min = Some(min); + } + _ => { + m.shrink_to_fit(); + s.shrink_to_fit(); + t.shrink_to_fit(rehash); + } + } + kept = [true; 3]; + } + _ => { + // Clearing keeps what it holds room for. + m.clear(); + s.clear(); + t.clear(); + out.push(u8::from( + m.capacity() >= before[0] && s.capacity() >= before[1] && t.capacity() >= before[2], + )); + } + } + let lens = [m.len(), s.len(), t.len()]; + let caps = [m.capacity(), s.capacity(), t.capacity()]; + let sizes = [m.allocation_size(), s.allocation_size(), t.allocation_size()]; + for i in 0..3 { + let floor = match (additional, kept_min) { + (Some(n), _) => lens[i] + n, + (_, Some(min)) => before[i].min(lens[i].max(min)), + _ => 0, + }; + holds(out, lens[i], caps[i], floor); + // An insert that grows it doubles it, at least. + let grew = op % 7 <= 2 && caps[i] > before[i]; + out.push(u8::from( + !grew || !kept[i] || before[i] == 0 || caps[i] >= 2 * before[i], + )); + if grew { + kept[i] = true; + } + out.push(u8::from(lens[i] == 0 || sizes[i] > 0)); + } + } +} + +/// `hashlink::LinkedHashMap`, by std's hasher, whose order is its own: each step of +/// `data` and then every entry, from the front. +pub fn linked(data: &[u8], out: &mut Vec) { + let mut m: LinkedHashMap = LinkedHashMap::with_hasher(RandomState::new()); + for step in data.chunks(2) { + let (op, arg) = (step[0], step.get(1).copied().unwrap_or(0)); + let key = u16::from(arg % 12); + match op % 8 { + 0 | 1 => put(out, m.insert(key, u32::from(op)).map_or(0x100, |v| v as usize)), + 2 => put(out, m.remove(&key).map_or(0x100, |v| v as usize)), + 3 => put(out, m.to_back(&key).map_or(0x100, |v| *v as usize)), + 4 => match arg & 1 { + 0 => put( + out, + m.pop_front() + .map_or(0x100, |(k, v)| usize::from(k) * 0x1000 + v as usize), + ), + _ => put( + out, + m.pop_back() + .map_or(0x100, |(k, v)| usize::from(k) * 0x1000 + v as usize), + ), + }, + 5 => { + put(out, m.front().map_or(0x100, |(k, _)| usize::from(*k))); + put(out, m.back().map_or(0x100, |(k, _)| usize::from(*k))); + } + 6 => put(out, m.insert_front(key, u32::from(op)).map_or(0x100, |v| v as usize)), + _ => m.retain(|k, v| { + *v += 1; + *k % 3 != u16::from(arg % 3) + }), + } + put(out, m.len()); + out.push(u8::from(m.capacity() >= m.len())); + } + while let Some((k, v)) = m.pop_front() { + put(out, usize::from(k)); + put(out, v as usize); + } +} diff --git a/ts/runtime/rt/collections.ts b/ts/runtime/rt/collections.ts index 47057b12b..cc891e3d0 100644 --- a/ts/runtime/rt/collections.ts +++ b/ts/runtime/rt/collections.ts @@ -1,5 +1,6 @@ // Collections, for rust2-ts output: std's `HashMap`, `HashSet`, `BTreeMap`, `BTreeSet`, -// `VecDeque`, `LinkedList`, and `BinaryHeap`, with `Ordering`, which their keys need. `rt.ts` +// `VecDeque`, `LinkedList`, and `BinaryHeap`, hashbrown's `HashTable`, which std's maps are +// built on, and `Ordering`, which their keys need. `rt.ts` // re-exports all of it. Each collection is one type whose functions are free functions // here, so a bundler keeps only those a caller reaches. // @@ -14,9 +15,9 @@ // A function that may drop a key, value, or item std's would, rather than return or keep // it, takes what drops each that does, as `k`, `v`, or `f`, and drops it where std's does. -import { ByteSlice, ByteVec, byteVecPush, byteVecReserve, type Capped, capHold, capOf, capReserve, capReserveExact, capSet, capShrink, type None, type Place, panic, type RangeBound, rangeCheck, sliceSort, vecExact, vecMax, vecReserve, vecWithCapacity } from "../rt.ts"; +import { ByteSlice, ByteVec, byteVecPush, byteVecReserve, type Capped, capHold, capOf, capReserve, capReserveExact, capSet, capShrink, Err, type None, Ok, type Place, panic, type RangeBound, type Result, rangeCheck, sliceSort, vecExact, vecMax, vecReserve, vecWithCapacity } from "../rt.ts"; import type { Hashing } from "./hash.ts"; -import { type Limbs, lt } from "./int.ts"; +import { type Limbs, lt, type u64 } from "./int.ts"; import { type Clones, DONE, @@ -409,11 +410,11 @@ interface Slot { } /** - * `HashMap`, and `HashSet` as a map to `undefined`. A key that is a JS primitive, - * a number, string, or boolean, is its own JS map key, as Rust compares it; a key that - * is an object goes by its hash, to a chain of the keys `Eq` tells apart. Iteration - * visits the first kind in insertion order and then the second by hash, an order Rust - * leaves unspecified. + * `HashMap`, and `HashSet` as a map to `undefined`, std's or hashbrown's. A key + * that is a JS primitive, a number, string, or boolean, is its own JS map key, as Rust + * compares it; a key that is an object goes by its hash, to a chain of the keys `Eq` + * tells apart. Iteration visits the first kind in insertion order and then the second by + * hash, an order Rust leaves unspecified. */ export interface HashMap { p: Map; @@ -422,6 +423,8 @@ export interface HashMap { n: number; /** How to find a key that is an object, or `undefined` if no key is one. */ readonly h: Hashing | undefined; + /** The capacity an operation last set it to, if any, which `hashCapacity` reads. */ + cap?: number; } /** `HashSet`. */ @@ -451,6 +454,59 @@ export function hashLen(m: HashMap): number { return m.p.size + m.n; } +// Capacity. std guarantees a hash collection's only past `capacity() >= len` and what +// `with_capacity`, `reserve`, and `shrink_to` ask for, so a map holds one, as `cap`, only +// where an operation sets it, and an insert never reads or writes it. Reading one doubles +// it while short of the length, as inserts grow std's, and a removal keeps what it read, +// as a table keeps its allocation. + +/** The least capacity a map holds once it holds anything. */ +const HASH_MIN = 3; + +/** `capacity` of a hash collection. */ +export function hashCapacity(m: HashMap): number { + const n = hashLen(m); + let c = m.cap ?? 0; + while (c < n) c = Math.max(c * 2, HASH_MIN); + return c; +} + +/** Keep the capacity `m` reads before it holds fewer. */ +function hold(m: HashMap): void { + if ((m.cap ?? 0) < hashLen(m)) m.cap = hashCapacity(m); +} + +/** `with_capacity`, of at most `max` entries, past which it panics, as hashbrown's does. */ +export function hashWithCapacity(n: number, max: number, h?: Hashing>): HashMap { + if (n > max) panic("Hash table capacity overflow"); + const m = hashNew(h); + if (n > 0) m.cap = n; + return m; +} + +/** + * `reserve`, of room for `additional` more, at least doubling, which panics past `max` + * entries. A `HashTable`'s takes its hasher, which it never calls, as it moves no item. + */ +export function hashReserve(m: HashMap, additional: number, max: number, _hasher?: (item: V) => u64): void { + const n = hashLen(m); + const cap = hashCapacity(m); + if (additional <= cap - n) return; + if (n + additional > max) panic("Hash table capacity overflow"); + m.cap = Math.min(Math.max(cap * 2, n + additional), max); +} + +/** `shrink_to`, and `shrink_to_fit` of 0: room for no fewer than `min` and what it holds. A table's hasher goes uncalled. */ +export function hashShrinkTo(m: HashMap, min: number, _hasher?: (item: V) => u64): void { + const target = Math.max(hashLen(m), min); + if (target < hashCapacity(m)) m.cap = target; +} + +/** hashbrown's `allocation_size` of entries `size` bytes each: a slot and a control byte for each it has room for. */ +export function hashAllocationSize(m: HashMap, size: number): number { + return hashCapacity(m) * (size + 1); +} + /** `HashMap::get`: the value there, or else `none`, the `None` of an `Option` of a value that can be `undefined`. */ export function hashGet(m: HashMap, key: K): V | undefined; export function hashGet(m: HashMap, key: K, none: None): V | None; @@ -574,6 +630,7 @@ export function hashAdd(m: HashSet, key: K, k?: (key: K) => void): boolean /** `HashMap::remove_entry`: the key and value there, if any. */ export function hashRemoveEntry(m: HashMap, key: K): [K, V] | undefined { + hold(m); if (!hashed(m, key)) { if (!m.p.has(key)) return undefined; const value = m.p.get(key) as V; @@ -606,6 +663,7 @@ export function hashRemove(m: HashMap, key: K, k?: (key: K) => void) export function hashRemove(m: HashMap, key: K, k: ((key: K) => void) | undefined, none: None): V | None; export function hashRemove(m: HashMap, key: K, k?: (key: K) => void, none?: None): V | None | undefined { if (!hashed(m, key)) { + hold(m); if (!m.p.has(key)) return none; const old = m.p.get(key); m.p.delete(key); @@ -621,6 +679,7 @@ export function hashRemove(m: HashMap, key: K, k?: (key: K) => void, /** `HashSet::remove`: whether `key` was there. The key it held drops. */ export function hashDelete(m: HashSet, key: K, k?: (key: K) => void): boolean { if (!hashed(m, key)) { + hold(m); if (!m.p.delete(key)) return false; k?.(key); return true; @@ -634,6 +693,7 @@ export function hashDelete(m: HashSet, key: K, k?: (key: K) => void): bool export function hashKey(m: HashSet, key: K, take?: boolean): K | undefined; export function hashKey(m: HashSet, key: K, take: boolean, none: None): K | None; export function hashKey(m: HashSet, key: K, take = false, none?: None): K | None | undefined { + if (take) hold(m); if (hashed(m, key) && take) { const entry = hashRemoveEntry(m, key); return entry === undefined ? none : entry[0]; @@ -657,6 +717,7 @@ export function hashDrop(m: HashMap, k: ((key: K) => void) | undefin /** `clear`, which drops each entry and leaves the map empty. */ export function hashClear(m: HashMap, k?: (key: K) => void, v?: (value: V) => void): void { + hold(m); if (k !== undefined || v !== undefined) hashDrop(m, k, v); m.p.clear(); m.o = undefined; @@ -675,6 +736,7 @@ export function hashRetainMut(m: HashMap, f: (key: K, value: Place(m: HashMap, f: (key: K, value: V | Place) => boolean, lend: boolean, k?: (key: K) => void, v?: (value: V) => void): void { + hold(m); for (const [key, value] of m.p) { if (f(key, lend ? new AtKey(m.p, key) : value)) continue; const now = lend ? (m.p.get(key) as V) : value; @@ -883,8 +945,9 @@ export function hashDrainKeys(m: HashMap, k?: (key: K) => void): Ite return new HashIter(taken(m), KEYS, k, undefined, true); } -/** What `m` held, which it no longer does. */ +/** What `m` held, which it no longer does, with the room it had. */ function taken(m: HashMap): HashMap { + hold(m); const out: HashMap = { p: m.p, o: m.o, n: m.n, h: m.h }; m.p = new Map(); hashClear(m); @@ -931,7 +994,7 @@ function vecExtending(c: C, len: number, items: Iter, add: (item: T) => * iterator's lower bound: all of it, or half rounded up if `m` holds any, which panics past * `max` entries, the most its table holds. */ -function hashReserve(m: HashMap, additional: number, max: number | undefined): void { +function extendReserve(m: HashMap, additional: number, max: number | undefined): void { if (max === undefined) return; const n = hashLen(m); if (n + (n === 0 ? additional : Math.ceil(additional / 2)) > max) panic("Hash table capacity overflow"); @@ -939,11 +1002,11 @@ function hashReserve(m: HashMap, additional: number, max: number | u /** * `extend` of a map with `[key, value]`s, and `collect` into one through `hashNew`, which - * reserves first as `hashReserve` does. Of equal keys, the first stays and each later one + * reserves first as `extendReserve` does. Of equal keys, the first stays and each later one * drops, then the value it replaced. */ export function hashExtend(m: HashMap, items: Iter<[K, V]>, k?: (key: K) => void, v?: (value: V) => void, take?: Take, max?: number): HashMap { - hashReserve(m, items.sizeHint()[0], max); + extendReserve(m, items.sizeHint()[0], max); const add = (item: [K, V]) => { const old = hashInsert(m, item[0], item[1], k); if (old !== undefined) v?.(old); @@ -953,7 +1016,7 @@ export function hashExtend(m: HashMap, items: Iter<[K, V]>, k?: (key /** `extend` of a set with keys, and `collect` into one, as `hashExtend`: a key already there drops. */ export function hashExtendKeys(m: HashSet, keys: Iter, k?: (key: K) => void, take?: Take, max?: number): HashSet { - hashReserve(m, keys.sizeHint()[0], max); + extendReserve(m, keys.sizeHint()[0], max); return extending(m, keys, (key) => hashAdd(m, key, k), take); } @@ -961,8 +1024,10 @@ export function hashExtendKeys(m: HashSet, keys: Iter, k?: (key: K) => export function hashClone(m: HashMap, key: (k: K) => K = itself, value: (v: V) => V = itself): HashMap { const out = hashNew(m.h); for (const [k, v] of m.p) out.p.set(key(k), value(v)); - for (const first of m.o?.values() ?? []) { - for (let s: Slot | undefined = first; s !== undefined; s = s.next) add(out, key(s.k), value(s.v)); + // Each clone keeps its original's hash, as hashbrown's copies its table without + // rehashing, and a table has no hasher to rehash by. + for (const [hash, first] of m.o ?? []) { + for (let s: Slot | undefined = first; s !== undefined; s = s.next) add(out, key(s.k), value(s.v), hash); } return out; } @@ -1011,6 +1076,158 @@ export function hashIsDisjoint(a: HashSet, b: HashSet): boolean { return true; } +// HashTable + +/** + * hashbrown's `HashTable`: a map of nothing to each item, under the hash its caller + * gives rather than one of its own, which the caller's equality tells apart. So the map's + * functions read, iterate, drain, drop, and size it, of its values. + */ +export type HashTable = HashMap; + +/** The JS map key of a hash: the number, or one past 2^53 its limbs make. */ +function tableKey(hash: u64): number { + return typeof hash === "number" ? hash : hash.hi * 0x1_0000_0000 + hash.lo; +} + +/** The slot of `t` under `hash` whose item `eq` accepts. */ +function tableSlot(t: HashTable, hash: number, eq: (item: T) => boolean): Slot | undefined { + for (let s = t.o?.get(hash); s !== undefined; s = s.next) if (eq(s.v)) return s; + return undefined; +} + +/** `HashTable::find`: the item under `hash` that `eq` accepts, or else `none`. */ +export function tableFind(t: HashTable, hash: u64, eq: (item: T) => boolean): T | undefined; +export function tableFind(t: HashTable, hash: u64, eq: (item: T) => boolean, none: None): T | None; +export function tableFind(t: HashTable, hash: u64, eq: (item: T) => boolean, none?: None): T | None | undefined { + const s = tableSlot(t, tableKey(hash), eq); + return s === undefined ? none : s.v; +} + +/** `HashTable::find_mut` of items JS copies: the place of the item `eq` accepts. */ +export function tableFindMut(t: HashTable, hash: u64, eq: (item: T) => boolean): Place | undefined { + return tableSlot(t, tableKey(hash), eq); +} + +/** + * A `HashTable`'s `Entry`, `OccupiedEntry`, `VacantEntry`, or `AbsentEntry`: the table, the + * hash, and the slot it found, which is the place of its item while it is occupied. + */ +export class TableEntry implements Place { + readonly t: HashTable; + private readonly h: number; + private s: Slot | undefined; + + constructor(t: HashTable, h: number, s: Slot | undefined) { + this.t = t; + this.h = h; + this.s = s; + } + + get v(): T { + return (this.s as Slot).v; + } + + set v(item: T) { + (this.s as Slot).v = item; + } + + /** Whether it is occupied. */ + get o(): boolean { + return this.s !== undefined; + } + + /** Insert `item`, which it holds then. */ + fill(item: T): this { + this.s = add(this.t, undefined, item, this.h); + return this; + } + + /** Take its item out of the table, which leaves it vacant. */ + take(): T { + const s = this.s as Slot; + hold(this.t); + unlink(this.t, this.h, s); + this.s = undefined; + return s.v; + } +} + +/** `HashTable::entry`: occupied by the item under `hash` that `eq` accepts, or else vacant. Its hasher goes uncalled. */ +export function tableEntry(t: HashTable, hash: u64, eq: (item: T) => boolean, _hasher?: (item: T) => u64): TableEntry { + const h = tableKey(hash); + return new TableEntry(t, h, tableSlot(t, h, eq)); +} + +/** `HashTable::find_entry`: `tableEntry`'s, as `Ok` if occupied and else as an `AbsentEntry` in `Err`. */ +export function tableFindEntry(t: HashTable, hash: u64, eq: (item: T) => boolean): Result, TableEntry> { + const e = tableEntry(t, hash, eq); + return e.o ? Ok(e) : Err(e); +} + +/** `HashTable::insert_unique`: the entry of `item`, inserted under `hash`. Its hasher goes uncalled. */ +export function tableInsertUnique(t: HashTable, hash: u64, item: T, _hasher?: (item: T) => u64): TableEntry { + return new TableEntry(t, tableKey(hash), undefined).fill(item); +} + +/** Whether `e` is `Entry::Occupied`. */ +export function tableOccupied(e: TableEntry): boolean { + return e.o; +} + +/** `Entry::insert` and `VacantEntry::insert`: `e`, holding `item` in place of what it held, which drops. */ +export function tableEntryInsert(e: TableEntry, item: T, f?: (item: T) => void): TableEntry { + if (!e.o) return e.fill(item); + const old = e.v; + e.v = item; + f?.(old); + return e; +} + +/** `Entry::or_insert`: `e`, holding `item` if it was vacant, or else what it held, and `item` drops. */ +export function tableOrInsert(e: TableEntry, item: T, f?: (item: T) => void): TableEntry { + if (!e.o) return e.fill(item); + f?.(item); + return e; +} + +/** `Entry::or_insert_with`: `e`, holding `make()`'s item if it was vacant. */ +export function tableOrInsertWith(e: TableEntry, make: () => T): TableEntry { + return e.o ? e : e.fill(make()); +} + +/** `Entry::and_modify`: `f` of its item, if occupied. */ +export function tableAndModify(e: TableEntry, f: (item: T) => void): TableEntry { + if (e.o) f(e.v); + return e; +} + +/** `Entry::and_modify` of items JS copies: `f` of its item's place, if occupied. */ +export function tableAndModifyMut(e: TableEntry, f: (item: Place) => void): TableEntry { + if (e.o) f(e); + return e; +} + +/** `OccupiedEntry::remove`: its item, and itself, vacant. */ +export function tableRemove(e: TableEntry): [T, TableEntry] { + return [e.take(), e]; +} + +/** `HashTable::retain`, which keeps each item `f` returns true for. Each goes, and drops by `d`, as `f` rejects it. */ +export function tableRetain(t: HashTable, f: (item: T) => boolean, d?: (item: T) => void): void { + retainHash(t, (_, item) => f(item as T), false, undefined, d); +} + +/** `HashTable::retain` of items JS copies: `f` takes the place of each. */ +export function tableRetainMut(t: HashTable, f: (item: Place) => boolean, d?: (item: T) => void): void { + retainHash(t, (_, item) => f(item as Place), true, undefined, d); +} + +/** `HashTable::drain`: every item, taken out at once, each it still holds dropping by `d` as it drops. */ +export function tableDrain(t: HashTable, d?: (item: T) => void): Iter { + return new HashIter(taken(t), VALUES, undefined, d, true); +} + // BTreeMap and BTreeSet /** @@ -2330,6 +2547,7 @@ export function entryInsert(e: Entry, value: V): V { /** `OccupiedEntry::remove_entry`. */ export function entryRemoveEntry(e: Entry): [K, V] { const m = e.m; + if (isHash(m)) hold(m); const value = entryGet(e); if (!isHash(m)) removeAt(m, e.l as Leaf, e.i); else if (e.s !== undefined) unlink(m, e.h as number, e.s); @@ -3565,10 +3783,10 @@ export function heapPart(le: Test, size?: number): Part, T> ); } -/** A `HashMap`, which inserts each entry as `hashExtend` does, and reserves as `hashReserve`. */ +/** A `HashMap`, which inserts each entry as `hashExtend` does, and reserves as `extendReserve`. */ export function hashPart(k?: (key: K) => void, v?: (value: V) => void, max?: number): Part, [K, V]> { return { - reserve: (m, additional) => hashReserve(m, additional, max), + reserve: (m, additional) => extendReserve(m, additional, max), one: (m, item) => { const old = hashInsert(m, item[0], item[1], k); if (old !== undefined) v?.(old); @@ -3580,7 +3798,7 @@ export function hashPart(k?: (key: K) => void, v?: (value: V) => void, max /** A `HashSet`, which adds each key as `hashExtendKeys` does. */ export function hashKeysPart(k?: (key: K) => void, max?: number): Part, K> { return { - reserve: (m, additional) => hashReserve(m, additional, max), + reserve: (m, additional) => extendReserve(m, additional, max), one: (m, key) => { hashAdd(m, key, k); return m; diff --git a/ts/src/lower/collections.rs b/ts/src/lower/collections.rs index cbede9d9d..75dbfc627 100644 --- a/ts/src/lower/collections.rs +++ b/ts/src/lower/collections.rs @@ -1,5 +1,6 @@ //! Collections, as `rt/collections.ts` gives them: `HashMap`, `HashSet`, `BTreeMap`, -//! `BTreeSet`, `VecDeque`, `LinkedList`, and `BinaryHeap`, with `Ordering` and `DefaultHasher`, which their keys need. +//! `BTreeSet`, `VecDeque`, `LinkedList`, and `BinaryHeap`, std's or hashbrown's, and +//! hashbrown's `HashTable`, with `Ordering` and `DefaultHasher`, which their keys need. //! //! A hash collection holds a key that is a JS primitive as its own JS map key, which JS //! compares as Rust's `Eq` does for numbers, strings, and booleans, and finds any other @@ -208,6 +209,8 @@ pub fn call<'tcx>( } let e = match std { Std::HashMap | Std::HashSet => hash(b, coll, call, owner, name, args, out), + Std::HashTable => table(b, coll, call, owner, name, args, out), + Std::TableEntry => table_entry(b, coll, name, args, call.span), Std::BTreeMap | Std::BTreeSet => btree(b, coll, call, owner, name, args), Std::VecDeque => deque(b, coll, call, owner, name, args), Std::LinkedList => list(b, coll, call, owner, name, args), @@ -387,6 +390,8 @@ pub fn place_call<'tcx>(b: &Body<'_, '_, 'tcx>, call: &Call<'tcx>) -> bool { 0, ), Std::HashMap => (&["get_mut", "iter_mut", "values_mut", "retain"], 1), + Std::HashTable => (&["find_mut", "iter_mut", "retain"], 0), + Std::TableEntry => (&["get_mut", "into_mut", "and_modify"], 0), Std::BTreeMap => (&["get_mut", "iter_mut", "values_mut", "range_mut", "retain"], 1), Std::Entry if is_vacant(b, coll) => (&["insert"], 1), Std::Entry => ( @@ -532,10 +537,17 @@ fn hash<'tcx>( let none = library::sentinel_option(b.t, call).is_some(); let mut values = args; let e = match (owner, name) { - (_, "new" | "with_capacity") | ("Default", "default") => { + (_, "new" | "with_hasher") | ("Default", "default") => { discard(values, out); hash_new(b, coll, span)? } + (_, "with_capacity" | "with_capacity_and_hasher") => { + let n = values.remove(0); + discard(values, out); + let mut values = vec![n, hash_max(b, coll, span)]; + values.extend(hashing(b, coll, key, span)?); + empty(b, Expr::rt("hashWithCapacity", values), coll, span) + } (_, "len") => Expr::rt("hashLen", values), (_, "is_empty") => Expr::rt("hashLen", values).binary("===", Expr::lit("0")), (_, "clear") => Expr::rt("hashClear", values), @@ -586,9 +598,8 @@ fn hash<'tcx>( vec![target, keys, Expr::undefined(), Expr::undefined(), max], ) } - (_, "reserve" | "shrink_to_fit") => { - discard(values, out); - Expr::undefined() + (_, "capacity" | "reserve" | "shrink_to" | "shrink_to_fit" | "allocation_size") => { + sized(b, coll, name, values, span) } ("Index", "index") => Expr::rt("hashIndex", values), ("Clone", "clone") => { @@ -613,6 +624,131 @@ fn hash<'tcx>( Some(e) } +/// What `name` of a hash collection or table of type `coll`, given `values`, reads or sets +/// of its capacity, as std guarantees it. +fn sized<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, name: &str, mut values: Vec, span: Span) -> Expr { + match name { + "capacity" => Expr::rt("hashCapacity", values), + "reserve" => { + values.push(hash_max(b, coll, span)); + Expr::rt("hashReserve", values) + } + "shrink_to_fit" => { + values.push(Expr::lit("0")); + Expr::rt("hashShrinkTo", values) + } + "shrink_to" => Expr::rt("hashShrinkTo", values), + _ => { + values.push(super::iter::size_of(b, stored(b, coll), span)); + Expr::rt("hashAllocationSize", values) + } + } +} + +/// A method of hashbrown's `HashTable`, or of a trait it implements: the runtime's map's of +/// its items, under the hash each is given. The runtime takes the `hasher` a method does, +/// and never calls it, as it never moves an item. +fn table<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + coll: Ty<'tcx>, + call: &Call<'tcx>, + owner: &str, + name: &str, + args: Vec, + out: &mut Vec, +) -> Option { + let span = call.span; + let item = arg(coll, 0); + // What a `&mut` to an item is: its place where JS would copy it. + let place = b.t.needs_place(item); + let mut values = args; + let e = match (owner, name) { + (_, "new") | ("Default", "default") => { + discard(values, out); + empty(b, Expr::rt("hashNew", vec![]), coll, span) + } + (_, "with_capacity") => { + values.push(hash_max(b, coll, span)); + empty(b, Expr::rt("hashWithCapacity", values), coll, span) + } + (_, "len") => Expr::rt("hashLen", values), + (_, "is_empty") => Expr::rt("hashLen", values).binary("===", Expr::lit("0")), + (_, "capacity" | "allocation_size") => sized(b, coll, name, values, span), + // The hasher last. + (_, "reserve" | "shrink_to" | "shrink_to_fit") => { + let hasher = values.pop()?; + match sized(b, coll, name, values, span) { + Expr::Call(f, mut values) => { + values.push(hasher); + Expr::Call(f, values) + } + e => e, + } + } + (_, "find_mut") if place => Expr::rt("tableFindMut", values), + (_, "find" | "find_mut") => Expr::rt("tableFind", values), + (_, "find_entry") => Expr::rt("tableFindEntry", values), + (_, "entry") => Expr::rt("tableEntry", values), + (_, "insert_unique") => Expr::rt("tableInsertUnique", values), + (_, "clear") => { + values.push(Expr::undefined()); + values.extend(b.t.droppers([item], span)?); + Expr::rt("hashClear", values) + } + (_, "retain") => { + values.extend(b.t.droppers([item], span)?); + Expr::rt(if place { "tableRetainMut" } else { "tableRetain" }, values) + } + (_, "iter_mut") if place => Expr::rt("hashValuesMut", values), + (_, "iter" | "iter_mut") => Expr::rt("hashValues", values), + (_, "drain") => { + values.extend(b.t.droppers([item], span)?); + Expr::rt("tableDrain", values) + } + ("Clone", "clone") => { + values.extend([Expr::undefined(), clone_fn(b, item, span)]); + Expr::rt("hashClone", values) + } + _ => return None, + }; + Some(e) +} + +/// A method of a `HashTable`'s `Entry`, `OccupiedEntry`, `VacantEntry`, or `AbsentEntry`. +fn table_entry<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + coll: Ty<'tcx>, + name: &str, + args: Vec, + span: Span, +) -> Option { + let item = arg(coll, 0); + let mut values = args; + let e = match name { + "insert" | "or_insert" => { + values.extend(b.t.droppers([item], span)?); + Expr::rt( + if name == "insert" { + "tableEntryInsert" + } else { + "tableOrInsert" + }, + values, + ) + } + "or_insert_with" => Expr::rt("tableOrInsertWith", values), + "and_modify" if b.t.needs_place(item) => Expr::rt("tableAndModifyMut", values), + "and_modify" => Expr::rt("tableAndModify", values), + // The entry is its item's place. + "get_mut" | "into_mut" if b.t.needs_place(item) => values.pop()?, + "get" | "get_mut" | "into_mut" => values.pop()?.member("v"), + "remove" => Expr::rt("tableRemove", values), + "into_table" => values.pop()?.member("t"), + _ => return None, + }; + Some(e) +} + /// The runtime's function of a set's operation or test `name`, of the runtime's `prefix` /// for its type: `hashUnion` of a `HashSet`'s `union`. fn set_op(prefix: &str, name: &str) -> Option { @@ -1830,10 +1966,15 @@ fn entry<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, name: &str, args: Vec Some(e) } -/// Whether `entry`, an `Entry`, is its variant `index`: `Occupied` while the map holds -/// its key. Each variant's entry is the `Entry` itself. -pub fn entry_test(adt: ty::AdtDef<'_>, index: rustc_abi::VariantIdx, entry: Expr) -> Expr { - let occupied = Expr::rt("entryOccupied", vec![entry]); +/// Whether `entry`, an `Entry` of a map or, as `std` says, of a `HashTable`, is its variant +/// `index`: `Occupied` while the map holds its key. Each variant's entry is the `Entry` itself. +pub fn entry_test(std: Std, adt: ty::AdtDef<'_>, index: rustc_abi::VariantIdx, entry: Expr) -> Expr { + let f = if std == Std::TableEntry { + "tableOccupied" + } else { + "entryOccupied" + }; + let occupied = Expr::rt(f, vec![entry]); match adt.variant(index).name.as_str() { "Occupied" => occupied, _ => occupied.not(), @@ -2618,6 +2759,8 @@ pub fn into_iter<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr, sp Std::BinaryHeap => "heapIter", Std::HashMap => "hashIter", Std::HashSet => "hashKeys", + Std::HashTable if places(0) => "hashValuesMut", + Std::HashTable => "hashValues", Std::BTreeMap => "btreeIter", Std::BTreeSet => "btreeKeys", Std::VecDeque => "dequeIter", diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index a46d90dfc..c0044d356 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -55,6 +55,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::PollFn | Std::HashMap | Std::HashSet + | Std::HashTable | Std::BTreeMap | Std::BTreeSet | Std::VecDeque @@ -332,6 +333,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Std::HashMap => ("hashDrop", vec![value, each(self, 0)?, each(self, 1)?]), Std::BTreeMap => ("btreeDrop", vec![value, each(self, 0)?, each(self, 1)?]), Std::HashSet => ("hashDrop", vec![value, each(self, 0)?]), + // Its items are the values of a map whose keys are nothing. + Std::HashTable => ("hashDrop", vec![value, Expr::undefined(), each(self, 0)?]), Std::BTreeSet => ("btreeDrop", vec![value, each(self, 0)?]), Std::VecDeque => ("dequeDrop", vec![value, each(self, 0)?]), Std::LinkedList => ("listDrop", vec![value, each(self, 0)?]), diff --git a/ts/src/lower/fmt.rs b/ts/src/lower/fmt.rs index fd4e47d56..2848f4b7a 100644 --- a/ts/src/lower/fmt.rs +++ b/ts/src/lower/fmt.rs @@ -395,6 +395,11 @@ pub fn std_impl<'tcx>( let item = b.fmt_fn(tr, args.type_at(0), span); rt("list", vec![Expr::rt("listIter", vec![value]), f, item]) } + // Its items, as a set. + (Std::HashTable, Trait::Debug) => { + let item = b.fmt_fn(tr, args.type_at(0), span); + rt("set", vec![Expr::rt("hashValues", vec![value]), f, item]) + } (std @ (Std::HashSet | Std::BTreeSet), Trait::Debug) => { let item = b.fmt_fn(tr, args.type_at(0), span); let keys = if std == Std::HashSet { "hashKeys" } else { "btreeKeys" }; diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index a37cd15f0..44be7fee3 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -156,6 +156,11 @@ pub enum Std { PeekMut, /// `rt.Entry`: a map's `Entry`, `OccupiedEntry`, or `VacantEntry`. Entry, + /// `rt.HashTable`: hashbrown's `HashTable`, an `rt.HashMap` of its items by the hash its + /// caller gives. + HashTable, + /// `rt.TableEntry`: a `HashTable`'s `Entry`, `OccupiedEntry`, `VacantEntry`, or `AbsentEntry`. + TableEntry, /// `rt.Ordering`: its discriminant, -1, 0, or 1. Ordering, /// `rt.Hasher`: `DefaultHasher`, which hash collections hash their keys with. @@ -981,6 +986,7 @@ pub(super) fn none_last(owner: &str, name: &str) -> bool { ) | ("BinaryHeap", "pop" | "peek") | ("HashSet", "get" | "take" | "replace") + | ("HashTable", "find") | ( "BTreeSet", "first" | "last" | "pop_first" | "pop_last" | "get" | "take" | "replace" @@ -1220,7 +1226,7 @@ pub fn call<'tcx>(b: &mut Body<'_, '_, 'tcx>, call: &Call<'tcx>, args: Vec Ok(e) => return e, Err(args) => args, }; - if !is_std(tcx, call.def_id) { + if !is_std(tcx, call.def_id) && !super::stock::as_std(tcx, call.def_id) { return None; } let (owner, name) = key(tcx, call.def_id)?; diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index cf08209fb..6c0b7bd04 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -368,6 +368,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { | Std::Option | Std::Result | Std::HashMap + | Std::HashTable | Std::BTreeMap | Std::VecDeque | Std::LinkedList diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 6896c4640..e36ed3a2c 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -469,8 +469,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { variant: VariantIdx, span: Span, ) -> Expr { - // `rt.Entry` is one class for either variant, which TypeScript narrows nothing by. - if matches!(ty.kind(), ty::Adt(adt, _) if matches!(Std::of(self.tcx, adt.did()), Some(Std::Entry))) { + // `rt.Entry` is one class for either variant, which TypeScript narrows nothing by, as is + // `rt.TableEntry`. + let entry = |adt: &ty::AdtDef<'_>| matches!(Std::of(self.tcx, adt.did()), Some(Std::Entry | Std::TableEntry)); + if matches!(ty.kind(), ty::Adt(adt, _) if entry(adt)) { return base; } let Some(variant) = self.variant_ty(ty, variant, span) else { @@ -593,7 +595,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Some(Std::Range | Std::RangeInclusive | Std::RangeTo | Std::RangeFrom | Std::RangeToInclusive) => { base.member(adt.non_enum_variant().fields[field].name.as_str()) } - Some(Std::Context | Std::NonNull | Std::Entry) => base, + Some(Std::Context | Std::NonNull | Std::Entry | Std::TableEntry) => base, // `TryFromIntError`'s `IntErrorKind`. Some(Std::TryFromIntError) => base.member("kind"), // The runtime's field of each of std's errors has its name. @@ -1201,7 +1203,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } } Some(Std::ControlFlow) => super::call::control_flow_test(self, scrut, ty, index, next), - Some(Std::Entry) => crate::lower::collections::entry_test(*adt, index, scrut.clone()), + Some(std @ (Std::Entry | Std::TableEntry)) => { + crate::lower::collections::entry_test(std, *adt, index, scrut.clone()) + } Some(Std::Cow) if let Some(elem) = crate::lower::library::cow_slice(tcx, ty) => { let borrows = crate::lower::library::cow_borrows(scrut.clone(), elem); match index.index() { diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 771f1a143..d0a8571d9 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -400,7 +400,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { .map(|(i, d)| (i, int.fold(d as i128) as u128)) .collect(); match Std::of(tcx, adt.did()) { - Some(Std::Option | Std::Poll | Std::Result | Std::Entry) => { + Some(Std::Option | Std::Poll | Std::Result | Std::Entry | Std::TableEntry) => { let scrut = self.path(scrut, "scrutinee", out); let first = self.variant_test(&scrut, ty, discrs[0].1, next, span); Expr::Cond( diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 7b45a068f..337667fc5 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1295,6 +1295,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | library::Std::BinaryHeap | library::Std::PeekMut | library::Std::Entry + | library::Std::HashTable + | library::Std::TableEntry | library::Std::Hasher | library::Std::BytesMut | library::Std::ThreadRng, @@ -1710,6 +1712,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | library::Std::BTreeMap | library::Std::Entry | library::Std::HashSet + | library::Std::HashTable + | library::Std::TableEntry | library::Std::BTreeSet | library::Std::VecDeque | library::Std::LinkedList @@ -1720,6 +1724,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { library::Std::BTreeMap => ("BTreeMap", 2), library::Std::Entry => ("Entry", 2), library::Std::HashSet => ("HashSet", 1), + library::Std::HashTable => ("HashTable", 1), + library::Std::TableEntry => ("TableEntry", 1), library::Std::BTreeSet => ("BTreeSet", 1), library::Std::BinaryHeap => ("BinaryHeap", 1), library::Std::PeekMut => ("PeekMut", 1), diff --git a/ts/src/lower/stock.rs b/ts/src/lower/stock.rs index 3fb9441e0..26a4453a7 100644 --- a/ts/src/lower/stock.rs +++ b/ts/src/lower/stock.rs @@ -1,7 +1,8 @@ //! Well-known crates that translation maps itself, as it maps std, so a consumer binds //! none of them: `bytes`' `Bytes`, which is the `rt.ByteSlice` a `&[u8]` is, and -//! `BytesMut`, which is `rt.BytesMut`, and `rand`'s thread-local generator, which draws -//! from the platform's cryptographic source. The rest of `bytes`, such as most of `Buf`'s +//! `BytesMut`, which is `rt.BytesMut`, `hashbrown`'s tables, which std's `HashMap` and +//! `HashSet` are, and `rand`'s thread-local generator, which draws from the platform's +//! cryptographic source. The rest of `bytes`, such as most of `Buf`'s //! provided methods of the crate's own buffers, translates from its MIR. `tracing`'s //! events are left out where they are written, by [`super::Translator::ignored_site`]. @@ -23,10 +24,30 @@ pub fn of(tcx: TyCtxt<'_>, did: DefId) -> Option { ("bytes", "Bytes") => Some(Std::Bytes), ("bytes", "BytesMut") => Some(Std::BytesMut), ("rand", "ThreadRng") => Some(Std::ThreadRng), + ("hashbrown", "HashMap") => Some(Std::HashMap), + ("hashbrown", "HashSet") => Some(Std::HashSet), + ("hashbrown", "HashTable") => Some(Std::HashTable), + // foldhash's, which hashbrown's are, seed each state at random, as std's do, so + // which hash a key gets is never observable. + ("hashbrown", "DefaultHashBuilder") => Some(Std::RandomState), + ("hashbrown", "DefaultHasher") => Some(Std::Hasher), + ("hashbrown", "Entry" | "OccupiedEntry" | "VacantEntry" | "AbsentEntry") => { + match tcx.opt_item_name(tcx.parent(did))?.as_str() { + "map" => (tcx.item_name(did).as_str() != "AbsentEntry").then_some(Std::Entry), + "table" => Some(Std::TableEntry), + _ => None, + } + } _ => None, } } +/// Whether `did` is an item of `hashbrown`, whose tables are std's, so its functions map +/// as std's do. +pub fn as_std(tcx: TyCtxt<'_>, did: DefId) -> bool { + is_stock(tcx, did) && tcx.crate_name(did.krate).as_str() == "hashbrown" +} + /// Whether `did` is an item of one of these crates, rather than of the crate translated, /// whatever its name. pub fn is_stock(tcx: TyCtxt<'_>, did: DefId) -> bool { @@ -36,13 +57,13 @@ pub fn is_stock(tcx: TyCtxt<'_>, did: DefId) -> bool { /// Whether `krate`, a dependency, is one of these crates, whose functions map, or translate /// only where [`maps`] says they don't. pub fn is_stock_crate(tcx: TyCtxt<'_>, krate: CrateNum) -> bool { - matches!(tcx.crate_name(krate).as_str(), "bytes" | "rand") + matches!(tcx.crate_name(krate).as_str(), "bytes" | "hashbrown" | "rand") } /// Whether `krate` is one of these crates of which nothing translates: `rand`, whose -/// generators would have to be its own. +/// generators would have to be its own, and `hashbrown`, whose tables are the runtime's. pub fn is_opaque_crate(tcx: TyCtxt<'_>, krate: CrateNum) -> bool { - tcx.crate_name(krate).as_str() == "rand" + matches!(tcx.crate_name(krate).as_str(), "hashbrown" | "rand") } /// Whether `did`, an item of `bytes`, has a mapping in place of its MIR: `Bytes`, @@ -227,6 +248,9 @@ pub fn call<'tcx>( return Err(args); }; let self_std = call.args.types().next().and_then(|t| b.t.std_of(t.peel_refs())); + if as_std(tcx, call.def_id) { + return Err(args); + } if is_stock(tcx, call.def_id) { return Ok(match tcx.crate_name(call.def_id.krate).as_str() { "bytes" => bytes(b, call, &owner, &name, args, out),