Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fba0d3a
chore(quest): claim quest/m2/ts-model/std-rest.md
kixelated Nov 1, 2026
db802a8
feat(ts): carry std's capacity through every Vec, VecDeque, and Binar…
kixelated Nov 1, 2026
512d8c7
perf(ts): keep a push's check for room small enough to inline
kixelated Nov 1, 2026
5941b40
style(ts): clippy and fmt
kixelated Nov 1, 2026
56c380a
Merge remote-tracking branch 'origin/main' into model-std-rest
kixelated Nov 1, 2026
c3daa4f
test(ts): refresh snapshots after merging main
kixelated Nov 1, 2026
7e2866a
fix(ts): a clone of an owning iterator, a forgotten PeekMut, and an u…
kixelated Nov 1, 2026
b189263
Merge remote-tracking branch 'origin/main' into model-std-rest
kixelated Nov 1, 2026
ec84a92
test(ts): refresh the lang harness snapshot after merging main
kixelated Nov 1, 2026
c4b42f0
docs(ts): migrate notes for the runtime's capacity arguments
kixelated Nov 1, 2026
b8d6db6
chore(quest): plan the rest of a vector's methods
kixelated Nov 1, 2026
239ae80
Merge remote-tracking branch 'origin/main' into model-std-rest
kixelated Nov 1, 2026
093e491
feat(ts): hold std's capacity guarantees, with pushes untouched
kixelated Nov 1, 2026
b049eb5
test(ts): refresh the lang snapshots for capacity
kixelated Nov 1, 2026
5fd3d99
test(ts): refresh the codec, stock, and task snapshots for capacity
kixelated Nov 1, 2026
4301ad8
fix(ts): keep the room reserve asked for as a vector's length drops
kixelated Nov 1, 2026
feadd7a
Merge remote-tracking branch 'origin/main' into model-std-rest
kixelated Nov 1, 2026
a39a6b3
test(ts): refresh the lang snapshots after merging main
kixelated Nov 1, 2026
944f68d
quest land: merge tested base
Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion quest/m2/ts-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,4 @@ Public API: the translator's output for moq-net's model.

## Required

- [The rest of std the model reaches](/quest/m2/ts-model/std-rest.md) - `Vec` and `VecDeque` capacity
- [hashbrown as the runtime's map](/quest/m2/ts-model/hashbrown.md) - hashlink's `hashbrown` tables translate as `rt.HashMap`, with std's capacity
9 changes: 3 additions & 6 deletions quest/m2/ts-model/hashbrown.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@ 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, as
[the rest of std](/quest/m2/ts-model/std-rest.md) decided for `Vec`'s capacity.
- `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.

## Required

- [The rest of std the model reaches](/quest/m2/ts-model/std-rest.md) - std's exact capacity, which hashbrown's growth reuses
29 changes: 0 additions & 29 deletions quest/m2/ts-model/std-rest.md

This file was deleted.

1 change: 1 addition & 0 deletions quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ generic std gap is planned here even when moq never calls it.
- [A B-tree's searches call Ord as std's do](/quest/m3/ts-btree-search-order.md) - a B-tree's lookups and inserts call a key's own `Ord` in std's order and number
- [Rc and Arc of a slice](/quest/m3/ts-shared-slices.md) - `Rc<[T]>` and `Arc<[T]>` from a `Vec`, a slice, or an iterator translate
- [The rest of a boxed slice's impls](/quest/m3/ts-boxed-slice-rest.md) - `clone_from`, `AsRef`, `AsMut`, `TryFrom` into a boxed array, and `Box<str>` into bytes translate for boxed slices
- [The rest of a vector's methods](/quest/m3/ts-vec-rest.md) - `append`, `resize`, `split_off`, `try_reserve`, and `Vec::from(VecDeque)` translate
- [The rest of a guard's methods](/quest/m3/ts-guard-rest.md) - `Debug` of `RefCell` and `RwLock` guards, `MutexGuard::map`, and the guards' `try_map` translate
- [Aborts in the differential](/quest/m3/ts-abort-differential.md) - the lang differential compares wasm32's aborts, so an `RwLock` locked again while held is tested
- [A reversed zip's per-pair cost](/quest/m3/ts-zip-rev-overhead.md) - a reversed by-index zip costs per pair what a forward one does, or the gap is explained
Expand Down
4 changes: 2 additions & 2 deletions quest/m3/ts-buf-mut-forward.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ moq-net's census reaches none of them, so this is m3 under the all-of-std decisi
- Mapping them as the buffer's own methods is the likely shape, but the provided bodies
grow through `chunk_mut`, which reserves 64 bytes when full, where `Vec<u8>`'s and
`BytesMut`'s overrides reserve what they write. Match the provided body's capacity once
capacity is exact ([std's capacity](/quest/m2/ts-model/std-rest.md),
[`BytesMut`'s](/quest/m3/ts-bytes-capacity.md)), not the override's.
`BytesMut`'s capacity is exact ([`BytesMut`'s](/quest/m3/ts-bytes-capacity.md)), not the
override's.

Public API: the translator's output.
7 changes: 3 additions & 4 deletions quest/m3/ts-deque-slices.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ the rest of std's iterators' `Debug`.
in `insert` and `remove`. std keeps `RawVec`'s capacity, moves the shorter segment as it
grows (`handle_capacity_increase`), and moves the shorter side in `insert`, `remove`, and
the rest. Every function that places items must place them where std's does.
- Open: a deque holds only what std guarantees of its capacity, doubling as it grows
(decided 2026-11-01 to keep pushes cheap), so splitting where std's does needs its exact
capacity, at a cost to measure, or the maintainer's call to drop the split.
- `Drain` writes its counts, `Drain(drain_len, idx, tail_len, remaining)`, which the
runtime's drain does not keep.

Public API: the translator's output.

## Required

- [The rest of std the model reaches](/quest/m2/ts-model/std-rest.md) - `VecDeque` carries std's capacity, which the split depends on
23 changes: 23 additions & 0 deletions quest/m3/ts-vec-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# [M] The rest of a vector's methods

## Goal

[rust2-ts](/ts/README.md) translates the `Vec` and `VecDeque` methods still refused, each
holding what std guarantees of its capacity, proven by differential tests against native
Rust.

## Plan

Found carrying capacity through every vector: these have no mapping, and std's bodies move
elements through raw pointers, so each refuses with its span:

- `Vec::append`, `resize`, `resize_with`, `split_off`, `extend_from_within`, and
`try_reserve`, and `VecDeque`'s `append`, `resize`, and `split_off`.
- `From<VecDeque<T>>` for `Vec<T>`.

A vector's capacity follows only std's guarantees, at least its length and what
`with_capacity` and `reserve` ask for, and doubles as it grows (decided with the
maintainer on 2026-11-01). No consumer calls them yet; they are planned here as part of
std.

Public API: the translator's output.
5 changes: 5 additions & 0 deletions ts/MIGRATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

- `rt.vecCapacity`, `rt.vecReserve`, `rt.vecReserveExact`, and `rt.dequeReserve` are now
`Vec::capacity`, `reserve`, and `reserve_exact` and `VecDeque::reserve` of a vector, its
element size in bytes last, as in `rt.vecReserve(v, additional, 4)`, not the arithmetic
an extend reserved by; `rt.vecPush` and `rt.vecGrow` are gone. `rt.dequeClone` and
`rt.heapClone` take the element size after the clone function.
- Every translation needs `tsc` 7.0 or later on `PATH`, not only one with bindings: it
type-checks the output, and fails at the Rust span of each error, where it succeeded
before.
Expand Down
14 changes: 10 additions & 4 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,7 @@ std's adaptors through `rt.iterFrom`. A caller calls `next()` for one item, or i
an `rt.Iter` with `for ... of`. Each `rt.Iter`'s `sizeHint` is std's `size_hint`, its upper
bound `undefined` for `None`, and a `collect` or `extend` into a `Vec`, `VecDeque`,
`BinaryHeap`, B-tree, or hash collection reserves by it where std's does, panicking with
std's capacity overflow past the most it holds. Capacity isn't modeled, so a collection
an `extend` starts from is taken as full. A tuple of these, of `String`s, `()`, or such
std's capacity overflow past the most it holds. A tuple of these, of `String`s, `()`, or such
tuples, collects, unzips, and extends as std's tuple impls do: a 1-tuple's collection by
its own `extend`, and a longer one's each reserving the lower bound, in field order, before
each item's fields go into them in order, as `rt.tupleExtend(t, it, [rt.vecPart(2),
Expand Down Expand Up @@ -743,7 +742,14 @@ they need.
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
whose length is a power of two. A `LinkedList` is nodes linked both ways, as std's are,
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,
as through pushes. One holds a capacity, as `cap`, only where `with_capacity`, `reserve`,
`shrink_to`, a `clone`, or a `collect` or `extend` set it, so a push never reads or
writes one. Reading it doubles it while short of the length, from the least std's
`RawVec` allocates for the element size, which a run of pushes grows std's to. A
`Vec<u8>`'s is its buffer's length. A `LinkedList` is nodes linked both ways, as std's are,
so `append` is O(1) and `split_off` walks from the nearer end; each node is the place
`iter_mut` and `front_mut` lend of an item JS copies. Dropping a whole map or set drops its entries in the
runtime's order, keys' for a B-tree, not std's node by node: std leaves that order
Expand Down Expand Up @@ -803,7 +809,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 collection's `capacity`, looking up a key by
that can be `undefined`, a `String`'s or hash collection'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
Expand Down
3 changes: 3 additions & 0 deletions ts/bench/moq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { push } from "./push.ts";
import { rescale } from "./rescale.ts";
import { ordered } from "./sorted.ts";
import { interleave, stamp } from "./timestamp.ts";
Expand Down Expand Up @@ -62,6 +63,8 @@ export function run(input: Uint8Array): Uint8Array | Promise<Uint8Array> {
return interleave(data);
case 12:
return order(selector, data);
case 13:
return push(data);
default:
throw new Error(`unknown workload ${op}`);
}
Expand Down
16 changes: 16 additions & 0 deletions ts/bench/push.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// The `push/` workloads' hand-written route: `bench.rs`'s op 13, as TypeScript grows a
// list: `push` onto an array, for a `VecDeque` too, as JS has no deque.

/** Run op 13, as `bench::run` does, for the count `data` starts with. */
export function push(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 v: number[] = [];
for (let i = 0; i < count; i++) v.push(i);
const out = new Uint8Array(16);
const view = new DataView(out.buffer);
view.setBigUint64(0, BigInt(v.length));
view.setBigUint64(8, BigInt(v[v.length >> 1] ?? 0));
return out;
}
7 changes: 6 additions & 1 deletion ts/bench/workloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
export interface Workload {
/** What is coded, such as `varint/quic/2B` or `frames/video/lite-07`, iterated, or converted. */
name: string;
op: "encode" | "decode" | "iterate" | "map" | "convert" | "add" | "compare" | "order" | "handoff";
op: "encode" | "decode" | "iterate" | "map" | "convert" | "add" | "compare" | "order" | "push" | "handoff";
/** Varints, frame headers, messages, items, keys, timestamps, integers, or handoffs per call, which each time is divided by. */
items: number;
input: Uint8Array;
Expand Down Expand Up @@ -99,6 +99,11 @@ export function workloads(encode: (input: Uint8Array) => Uint8Array): Workload[]
orders.forEach((name, selector) => {
out.push({ name: `order/${name}`, op: "order", items: count, input: Uint8Array.of(12, selector, ...u64(BigInt(count))) });
});

// `bench.rs`'s op 13: pushes, which grow a vector by std's capacity.
["vec", "deque"].forEach((name, selector) => {
out.push({ name: `push/${name}`, op: "push", items: count, input: Uint8Array.of(13, selector, ...u64(BigInt(count))) });
});
return out;
}

Expand Down
38 changes: 37 additions & 1 deletion ts/fixtures/codec/expected/bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type * as EncodeError from "./coding/encode.EncodeError.ts";
import * as Encoder from "./coding/encode.Encoder.ts";
import { zigzag as varint_zigzag, unzigzag } from "./coding/varint.ts";
import * as Form from "./coding/varint.Form.ts";
import { unwrapOr$11c0we2 as option_unwrapOr$11c0we2, map$1cf8u0y as option_map$1cf8u0y } from "./core/option.ts";
import { unwrapOr$11c0we2 as option_unwrapOr$11c0we2, map$1cf8u0y as option_map$1cf8u0y, mapOr$1jt76jv as option_mapOr$1jt76jv } from "./core/option.ts";
import { form, liteVersion, putU64 as harness_putU64, putUsize, takeU64, decodeError, encodeError } from "./harness.ts";
import * as Group from "./lite/group.Group.ts";
import type * as lite_version_Version from "./lite/version.Version.ts";
Expand Down Expand Up @@ -45,6 +45,8 @@ export function run(input: rt.ByteSlice): rt.ByteVec {
interleave(new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out);
} else if (op === 12) {
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 {
decoded(decodeGroup(new rt.ByteSlice(input.data, input.start + 2, input.length - 2), liteVersion(selector), out), out);
}
Expand Down Expand Up @@ -384,6 +386,40 @@ export function order(selector: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): voi
}
}

/**
* `count` integers pushed onto a `Vec`, or a `VecDeque` if `selector` is 1: its length
* and middle item.
*/
export function push(selector: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void {
let _13!: [rt.usize, rt.u32 | undefined];
let _18!: rt.u32;
let _37!: rt.u32;
const count2: rt.u32 = rt.u64Low(rt.u64Min(takeU64(data)[0], 65536));
if (selector === 1) {
const q: rt.VecDeque<rt.u32> = rt.dequeNew() as rt.VecDeque<rt.u32>;
let iterStart: rt.u32 = 0;
const iterEnd: rt.u32 = count2;
while ((_18 = iterStart++) < iterEnd) {
rt.dequePushBack(q, _18);
}
_13 = [q.n, rt.dequeGet(q, rt.usizeDiv(q.n, 2))] as [rt.usize, rt.u32 | undefined];
} else {
const v: rt.u32[] = [];
let iterStart2: rt.u32 = 0;
const iterEnd2: rt.u32 = count2;
while ((_37 = iterStart2++) < iterEnd2) {
v.push(_37);
}
_13 = [v.length, rt.sliceGet(v, rt.usizeDiv(v.length, 2))] as [rt.usize, rt.u32 | undefined];
}
const len: rt.usize = _13[0];
const middle: rt.u32 | undefined = _13[1];
putUsize(out, len);
harness_putU64(out, option_mapOr$1jt76jv(middle, 0, (a0: rt.u32): rt.u64 => {
return a0 as rt.u64;
}));
}

/**
* One iterator chain over every byte, picked by `chain`, with closures that cost little
* next to the iterator protocol they run through.
Expand Down
2 changes: 1 addition & 1 deletion ts/fixtures/codec/expected/coding/version.Versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function encode$coding_encode_Encode_lite_version_Version(self: Versions,
}

export function from$convert_From_array2_coding_version_Version(vs: coding_version_Version.Version[]): Versions {
return { _0: vs.slice() };
return { _0: rt.vecExact(vs.slice(), 8) };
}

//# sourceMappingURL=version.Versions.ts.map
7 changes: 7 additions & 0 deletions ts/fixtures/codec/expected/core/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ export function map$1cf8u0y(self: rt.u64 | undefined, f: (a0: rt.u64) => rt.u64)
return f(self);
}

export function mapOr$1jt76jv(self: rt.u32 | undefined, default_: rt.u64, _: (a0: rt.u32) => rt.u64): rt.u64 {
if (self === undefined) {
return default_;
}
return self as rt.u64;
}

export function okOr$1lamf4s(self: rt.u64 | undefined, err: EncodeError.EncodeError): rt.Result<rt.u64, EncodeError.EncodeError> {
if (self === undefined) {
return rt.Err(err);
Expand Down
32 changes: 29 additions & 3 deletions ts/fixtures/codec/src/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
//! decode's input. Op 6 runs iterator chains over its bytes instead, as moq-net's model
//! 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, and
//! op 12 orders integers by std's own `Ord`.
//! 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.

use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, 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};
Expand Down Expand Up @@ -37,6 +37,7 @@ pub fn run(input: &[u8]) -> Vec<u8> {
10 => stamp(data, &mut out),
11 => interleave(data, &mut out),
12 => order(selector, data, &mut out),
13 => push(selector, data, &mut out),
_ => decoded(decode_group(data, lite_version(selector), &mut out), &mut out),
}
out
Expand Down Expand Up @@ -252,6 +253,31 @@ fn order(selector: u8, data: &[u8], out: &mut Vec<u8>) {
}
}

/// `count` integers pushed onto a `Vec`, or a `VecDeque` if `selector` is 1: its length
/// and middle item.
fn push(selector: u8, data: &[u8], out: &mut Vec<u8>) {
let (count, _) = take_u64(data);
let count = count.min(1 << 16) as u32;
let (len, middle) = match selector {
1 => {
let mut q = VecDeque::new();
for i in 0..count {
q.push_back(i);
}
(q.len(), q.get(q.len() / 2).copied())
}
_ => {
let mut v = Vec::new();
for i in 0..count {
v.push(i);
}
(v.len(), v.get(v.len() / 2).copied())
}
};
put_usize(out, len);
put_u64(out, middle.map_or(0, u64::from));
}

/// One iterator chain over every byte, picked by `chain`, with closures that cost little
/// next to the iterator protocol they run through.
fn iterate(chain: u8, data: &[u8], out: &mut Vec<u8>) {
Expand Down
Loading