diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index f525b8295..8a3f99fa5 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -37,7 +37,7 @@ Decided with the maintainer on 2026-10-27, from a census of the model's 22 publi safe `cfg` twins in moq, so differential tests cover it, and a dependency containing `unsafe` is bound by the consumer. These are stopgaps: decided with the maintainer on 2026-10-27, a stock crate translates with no annotations or config, so - [unsafe code the translator can model](/quest/m3/ts-unsafe.md) and + [unsafe code the translator can model](/quest/m3/ts-unsafe/README.md) and [trait objects](/quest/m3/ts-dyn.md) remove them. Only true platform boundaries (clocks and DEFLATE) stay bound. - Each child measures this target by the census of `ts/moq/model.toml`, rooted at those @@ -52,7 +52,7 @@ model, so each child below unblocks all of them together. The rest is moq's, for research quest: kio's `log.rs` reads bytes as its segments and retires them through crossbeam-epoch, whose tagged pointers and inline closures (36 refusals) translation refuses for good, and smallvec's union over a type parameter and raw parts (10) wait on -[unsafe](/quest/m3/ts-unsafe.md), so both need safe `cfg(rust2_ts)` twins or bindings +[unsafe](/quest/m3/ts-unsafe/README.md), so both need safe `cfg(rust2_ts)` twins or bindings there, as does the `unsafe` slice of `model/frame.rs`'s `Filled`. Decided with the maintainer on 2026-11-04, from a census at the bumped pin diff --git a/quest/m3/README.md b/quest/m3/README.md index d4be3829d..5d8006a77 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -15,7 +15,7 @@ generic std gap is planned here even when moq never calls it. - [Retrial Cranelift on moq's tests](/quest/m3/cranelift-unwind.md) - waits on upstream: moq's tests pass under Cranelift, so its CI can weigh it again - [Relink without rebuilding](/quest/m3/relink.md) - measure upstream's skip of dependency-only rebuilds on moq's edits once it reaches nightly -- [Unsafe code the translator can model](/quest/m3/ts-unsafe.md) - a dependency using only `unsafe` the translator can model translates from source +- [Unsafe code the translator can model](/quest/m3/ts-unsafe/README.md) - a dependency using only `unsafe` the translator can model translates from source - [Trait objects' TypeId](/quest/m3/ts-dyn.md) - `Any::type_id` and `TypeId` translate with Rust's equality, order, and hash, so a consumer neither rewrites nor binds them - [Waiting: drop the lang fixture's mul_add allowance](/quest/m3/ts-fma-allowance.md) - the differential fails on any `mul_add` difference again once wasm32's `fma` rounds exactly - [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library diff --git a/quest/m3/ts-runtime-throws.md b/quest/m3/ts-runtime-throws.md index 5991b9505..7cac8d006 100644 --- a/quest/m3/ts-runtime-throws.md +++ b/quest/m3/ts-runtime-throws.md @@ -24,7 +24,9 @@ The rest are gaps. At `main` they include a panic its hook cannot locate ([#556](https://github.com/moq-dev/rust2/pull/556)), a pointer cast to `()` that is not an `Arc`'s or `Rc`'s (`rs.arcErase`, [#562](https://github.com/moq-dev/rust2/pull/562), [#565](https://github.com/moq-dev/rust2/pull/565)), `offset_from` of one Rust allocation -JS keeps in two arrays, `Bytes::slice_ref` of other storage, `alloc`'s bytes read as +JS keeps in two arrays, a pointer moved past the array JS keeps it in (`AtIndex.reach`, a +`Vec`'s spare capacity, which [its own quest](/quest/m3/ts-unsafe/vec-spare-capacity.md) +owns, or a pointer kept in a field), `Bytes::slice_ref` of other storage, `alloc`'s bytes read as values, ranges stepped without their type, and fmt widths written as a placeholder. `git grep 'rust2-ts: ' ts/runtime` lists them; classify each before fixing. The pointer comparisons [#584](https://github.com/moq-dev/rust2/pull/584) refuses at translation are @@ -34,4 +36,4 @@ Public API: the translator's output, and which crates translate. ## Related -- [Unsafe code the translator can model](/quest/m3/ts-unsafe.md) - the raw-pointer model several of these throws guard +- [Unsafe code the translator can model](/quest/m3/ts-unsafe/README.md) - the raw-pointer model several of these throws guard; its 2026-11-01 run-time failures now follow this quest's rule diff --git a/quest/m3/ts-unsafe.md b/quest/m3/ts-unsafe.md deleted file mode 100644 index 889c4408c..000000000 --- a/quest/m3/ts-unsafe.md +++ /dev/null @@ -1,83 +0,0 @@ -# [L] Unsafe code the translator can model - -## Goal - -[rust2-ts](/ts/README.md) translates the `unsafe` operations it can give exact semantics in -JS, so a dependency using only those translates from source instead of being bound by -hand. What is left: unions over a type parameter, a raw pointer to a value JS copies -taken through a reference, and pointers into an array of values JS keeps as objects. - -## Plan - -Decided with the maintainer on 2026-10-27: a dependency containing `unsafe` is bound by -the consumer [at first](/ts/README.md#bindings), and moq gives its own `unsafe` safe -`cfg(rust2_ts)` twins. Both are stopgaps, since a stock crate should translate with no -annotations or config; this quest is what retires them. - -Decided with the maintainer on 2026-10-28, typed pointers: a raw pointer is what its -reference is, the object, an `rs.Place`, or `rs.NULL`, and a pointer into a slice or `Vec` -is `{ array, index }`. Byte reinterpretation, pointer-to-integer casts, and raw allocation -are refused with a span. [#201](https://github.com/moq-dev/rust2/pull/201) landed `Box` -to raw and back, `NonNull`, `MaybeUninit`, raw reads and writes, `get_unchecked`, and -unions whose overlapping fields share a type, which took hashlink from 375 refusals to -none of its own `unsafe`. - -- An `unsafe` operation outside the modeled set is refused with its span, as today. -- Undefined behavior in Rust need not be reproduced, but a translated program never - silently differs from native where Rust's behavior is defined. -- Element pointers into arrays of values JS copies are `rs.AtIndex`, `{ array, index }`, - which `add`, `offset`, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` act on. - Decided with the maintainer on 2026-11-01: where one body shows a pointer crossing what - JS keeps apart (a value in no array, a copied shared subslice), translation refuses it - with a span; a pointer from elsewhere, such as a field, fails there loudly at run time. - A pointer to an element JS keeps as an object, such as a tuple or the crate's struct, is - that object, which knows no array it is in, so arithmetic on one stays refused: - hashbrown's buckets and kio's `log.rs` reach it. -- Decided with the maintainer on 2026-11-01, typed allocation: memory from `alloc` for a - `Layout::array::(n)`, or an equal layout of `T`'s size and alignment, cast to `*mut T` - is a fresh JS array of `T`s, which `realloc` copies and `dealloc` frees. Untyped and - byte allocation stay refused. smallvec spills to it, and `Vec::from_raw_parts` of it is - left. -- Decided with the maintainer on 2026-11-02: a raw slice pointer is `rs.RawSlice`, its - first element's pointer and its length, read through only where Rust reads, and an empty - slice from a pointer into no array keeps that pointer through every part of it. -- A union over a type parameter, whose layout has no fixed offsets until an instance - fixes them (smallvec's `SmallVecData`): lay out each instance instead of the identity. -- `&raw const *r` where `r` is a `&T` to a value JS copies or a library object, such as - foldhash's seed and arrayvec's `as_ptr`: the reference is the value, not where it is - kept, so it needs the place the reference came from. -- A `Vec`'s spare capacity written through `as_mut_ptr` and committed by `set_len`: - `set_len` is refused but a byte `Vec`'s, and a write past the length fails loudly at run - time, since the JS array would grow by it. The spare slots need to live outside the array's length - until `set_len` takes them. -- `UnsafeCell::raw_get`, `from_mut`, and `replace`, refused with their span: none is in - moq's census at the pin. -- A byte pointer moved past its own `Uint8Array` view into the rest of the buffer, such as - a byte vector's spare capacity: it fails loudly at run time, since each view knows only - its own range. One shared view per buffer, with each pointer's byte offset into it, - closes it. Found by [#455](https://github.com/moq-dev/rust2/pull/455). -- A slice's pointer cast to an array's of its length, `*(s as *const [u8] as *const - [u8; N])`, which `bytes`' `Buf::get_u16` and wider read a crate's own buffer through - ([#222](https://github.com/moq-dev/rust2/pull/222)): the bytes keep their type, so - this reinterprets nothing. Those getters also need `<[u8]>::get` and `get_mut` of a - range, and `chunks_vectored` needs `std::io::IoSlice`: the rest of moq-net's refusals - in `bytes`. -- The integers' `unchecked_add`, `unchecked_sub`, `unchecked_mul`, `unchecked_neg`, - `unchecked_shl`, `unchecked_shr`, `unchecked_funnel_shl`, and `unchecked_funnel_shr`, and - `NonZero::new_unchecked`, refused with their span: each is its checked form where Rust - defines it, but a debug build aborts on a violated precondition where the checked form - panics with another message. -- `BinaryHeap` and `PeekMut` translate from std's MIR, whose `Hole` moves elements - through raw pointers into the `Vec`'s storage, replacing `rs.BinaryHeap` and - `rs.PeekMut`, the hand-written port of std's sifts that - [#252](https://github.com/moq-dev/rust2/pull/252) mapped them to and that must track - std's by hand. The lang fixture's `orders` differential case proves the translation - still matches std's comparisons, pop order, and drops. -- `Vec::drain` translates from std's MIR, whose `Drain` reads its range out and moves the - tail back through raw pointers into the `Vec`'s storage, replacing `rs.vecDrain` and - `rs.byteVecDrain`, the runtime port that - [the slice gaps' PR](https://github.com/moq-dev/rust2/pull/259) mapped it to. The lang - fixture's `slices::drains` differential case proves the translation still matches std's - items, drops, and what a forgotten drain leaves. - -Public API: the translator's output. diff --git a/quest/m3/ts-unsafe/README.md b/quest/m3/ts-unsafe/README.md new file mode 100644 index 000000000..fbe362cfd --- /dev/null +++ b/quest/m3/ts-unsafe/README.md @@ -0,0 +1,80 @@ +# Unsafe code the translator can model + +## Goal + +[rust2-ts](/ts/README.md) translates every `unsafe` construct it can give exact semantics +in JS, each proven by differential tests against native Rust, so a stock crate's `unsafe` +never needs a hand binding or a moq `cfg(rust2_ts)` twin. + +## Plan + +Decided with the maintainer on 2026-10-27: a dependency containing `unsafe` is bound by +the consumer [at first](/ts/README.md#bindings), and moq gives its own `unsafe` safe +`cfg(rust2_ts)` twins. Both are stopgaps, since a stock crate should translate with no +annotations or config; this epic is what retires them. + +Decided with the maintainer on 2026-10-28, typed pointers: a raw pointer is what its +reference is, the object, an `rs.Place`, or `rs.NULL`, and a pointer into a slice or `Vec` +is `{ array, index }`. Byte reinterpretation, pointer-to-integer casts, and raw allocation +are refused with a span. [#201](https://github.com/moq-dev/rust2/pull/201) landed `Box` +to raw and back, `NonNull`, `MaybeUninit`, raw reads and writes, `get_unchecked`, and +unions whose overlapping fields share a type, which took hashlink from 375 refusals to +none of its own `unsafe`. + +- An `unsafe` operation outside the modeled set is refused with its span. +- Undefined behavior in Rust need not be reproduced, but a translated program never + silently differs from native where Rust's behavior is defined. +- Element pointers into arrays of values JS copies are `rs.AtIndex`, `{ array, index }`, + which `add`, `offset`, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` act on. + Decided with the maintainer on 2026-11-01: where one body shows a pointer crossing what + JS keeps apart (a value in no array), translation refuses it with a span. A pointer to + an element JS keeps as an object, such as a tuple or the crate's struct, is that object, + which knows no array it is in, so arithmetic on one stays refused: hashbrown's buckets + and kio's `log.rs` reach it. +- Decided with the maintainer on 2026-11-01, typed allocation: memory from `alloc` for a + `Layout::array::(n)`, or an equal layout of `T`'s size and alignment, cast to `*mut T` + is a fresh JS array of `T`s, which `realloc` copies and `dealloc` frees. Untyped and + byte allocation stay refused. smallvec spills to it, and + [`Vec::from_raw_parts` of it](/quest/m3/ts-unsafe/vec-from-raw-parts.md) is left. +- Decided with the maintainer on 2026-11-02: a raw slice pointer is `rs.RawSlice`, its + first element's pointer and its length, read through only where Rust reads, and an empty + slice from a pointer into no array keeps that pointer through every part of it. + +Decided with the maintainer on 2026-11-06, splitting this quest into one child per +construct still open at `b3df54b6c`, all in m3: + +- ✅ Goal: every `unsafe` construct rust2-ts can make exact, one quest each. + Rejected: only the constructs moq's dependencies hit. +- ✅ All children in m3. Rejected: spare capacity in m2; smallvec's constructs in m2. + No child blocks moq: the model's only hits are smallvec's (7 of its 10 refusals), and + moq drops or binds smallvec. +- `Vec::from_raw_parts` of a typed allocation is its own child, restored on review of + [#618](https://github.com/moq-dev/rust2/pull/618): the spare-capacity child fills an + existing `Vec`, not one adopting an allocation. +- The 2026-11-01 clauses that let a pointer from elsewhere, a `Vec`'s spare capacity, and + a byte pointer past its view fail loudly at run time are superseded by + [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md) (2026-11-04, #597): where + Rust defines the access it translates exactly or is refused with a span, and only a + throw guarding UB stays, as a precondition abort. +- Done and dropped: a byte pointer past its own `Uint8Array` view. Byte pointers index the + whole buffer since [#400](https://github.com/moq-dev/rust2/pull/400) and + [#576](https://github.com/moq-dev/rust2/pull/576), and a byte `Vec` keeps its + capacity since [#555](https://github.com/moq-dev/rust2/pull/555) and + [#568](https://github.com/moq-dev/rust2/pull/568). `bytes`' `Buf::get_u16` and wider + map to the runtime since [#447](https://github.com/moq-dev/rust2/pull/447). + +Census at the moq pin on 2026-11-06: model 82, kio 76, moq-net 409 refusals. + +Public API: the translator's output. + +## Required + +- [A Vec's spare capacity](/quest/m3/ts-unsafe/vec-spare-capacity.md) - writes through `as_mut_ptr` past the length, committed by `set_len`, translate for any `Vec` +- [A raw pointer behind a reference](/quest/m3/ts-unsafe/raw-behind-ref.md) - `&raw const *r` of a value JS copies points where the reference came from +- [Vec::from_raw_parts of a typed allocation](/quest/m3/ts-unsafe/vec-from-raw-parts.md) - a `Vec` adopts an `alloc`ed array of `T`s, with its length, capacity, and drops +- [Unions over a type parameter](/quest/m3/ts-unsafe/generic-unions.md) - each instance of a generic union is laid out with its own offsets +- [Vec::drain from std's MIR](/quest/m3/ts-unsafe/vec-drain-mir.md) - std's `Drain` translates, replacing the runtime's port +- [BinaryHeap from std's MIR](/quest/m3/ts-unsafe/binary-heap-mir.md) - std's `BinaryHeap` and `PeekMut` translate, replacing the runtime's port +- [A slice's pointer cast to an array's](/quest/m3/ts-unsafe/array-casts.md) - `*(s as *const [u8] as *const [u8; N])` in a crate's own code, and `IoSlice` +- [Unchecked integer operations](/quest/m3/ts-unsafe/unchecked-ops.md) - `unchecked_*` and `NonZero::new_unchecked` translate, aborting on a violated precondition +- [UnsafeCell's raw accessors](/quest/m3/ts-unsafe/unsafecell.md) - `UnsafeCell::raw_get`, `from_mut`, and `replace` translate diff --git a/quest/m3/ts-unsafe/array-casts.md b/quest/m3/ts-unsafe/array-casts.md new file mode 100644 index 000000000..0274386ea --- /dev/null +++ b/quest/m3/ts-unsafe/array-casts.md @@ -0,0 +1,18 @@ +# [S] A slice's pointer cast to an array's + +## Goal + +[rust2-ts](/ts/README.md) translates `*(s as *const [u8] as *const [u8; N])` in a crate's +own code, and `std::io::IoSlice` (`IoSlice::new`, `Buf::chunks_vectored`), proven by +differential tests against native Rust. Today the cast is refused as ``a cast from a +pointer to `[u8]` to one to `[u8; 2]`, which reads the bytes of one type as another`` +(`ts/src/lower/pointer.rs`), and `IoSlice` as an unsupported type +(`ts/src/lower/mod.rs`, `ts/src/lower/dynamic.rs`). + +## Plan + +The bytes keep their type, so the cast reinterprets nothing. `bytes`' own `get_u16` and +wider already map to the runtime since [#447](https://github.com/moq-dev/rust2/pull/447), +and `<[u8]>::get` and `get_mut` of a range translate. No consumer at the moq pin. + +Public API: the translator's output. diff --git a/quest/m3/ts-unsafe/binary-heap-mir.md b/quest/m3/ts-unsafe/binary-heap-mir.md new file mode 100644 index 000000000..b58cf4dcb --- /dev/null +++ b/quest/m3/ts-unsafe/binary-heap-mir.md @@ -0,0 +1,21 @@ +# [M] BinaryHeap from std's MIR + +## Goal + +[rust2-ts](/ts/README.md) translates `BinaryHeap` and `PeekMut` from std's MIR, whose +`Hole` moves elements through raw pointers into the `Vec`'s storage, replacing +`rs.BinaryHeap` and `rs.PeekMut`, the hand-written port of std's sifts that +[#252](https://github.com/moq-dev/rust2/pull/252) mapped them to +(`ts/src/lower/collections.rs`). The lang fixture's `orders` differential case proves it +still matches std's comparisons, pop order, and drops. + +## Plan + +No consumer at the moq pin; the port works but must track std by hand. The B-tree port of +[#577](https://github.com/moq-dev/rust2/pull/577) is the precedent. + +Public API: the translator's output. + +## Required + +- [A Vec's spare capacity](/quest/m3/ts-unsafe/vec-spare-capacity.md) - std's `PeekMut` calls `set_len` diff --git a/quest/m3/ts-unsafe/generic-unions.md b/quest/m3/ts-unsafe/generic-unions.md new file mode 100644 index 000000000..6f89eb163 --- /dev/null +++ b/quest/m3/ts-unsafe/generic-unions.md @@ -0,0 +1,18 @@ +# [M] Unions over a type parameter + +## Goal + +[rust2-ts](/ts/README.md) translates a union whose fields' layout depends on a type +parameter, such as smallvec's `SmallVecData`, by laying out each instance rather than +the generic definition, proven by differential tests against native Rust. Today it is +refused: ``a union field of `_`, whose layout is not known: the type `A` does not have a +fixed layout`` (`ts/src/lower/unions.rs`). + +## Plan + +Unions whose overlapping fields share a type translate since +[#201](https://github.com/moq-dev/rust2/pull/201); an instance fixes the offsets the +generic definition lacks. Smallvec (`lib.rs:840`) is the only hit at the moq pin, once +each in the model, kio, and moq-net. + +Public API: the translator's output. diff --git a/quest/m3/ts-unsafe/raw-behind-ref.md b/quest/m3/ts-unsafe/raw-behind-ref.md new file mode 100644 index 000000000..9fecd0d2e --- /dev/null +++ b/quest/m3/ts-unsafe/raw-behind-ref.md @@ -0,0 +1,23 @@ +# [M] A raw pointer behind a reference + +## Goal + +[rust2-ts](/ts/README.md) translates `&raw const *r` and `ptr::read(r)` where `r` is a `&T` +to a value JS copies or a library object, so the pointer is where the reference came +from, proven by differential tests against native Rust. Today it is refused: ``a raw +pointer to `T` behind a reference, which is the object and not where it is kept`` +(`ts/src/lower/mir/rvalue.rs`). + +## Plan + +The reference is the value, not where it is kept, so the lowering needs the place the +reference was made from. Since [#576](https://github.com/moq-dev/rust2/pull/576) and +[#584](https://github.com/moq-dev/rust2/pull/584), a slice's reference knows its +`{data, start}`, which is that place for elements. + +At the moq pin it refuses 6 times in the model and 8 in moq-net, all smallvec's `Drain` +(`lib.rs:383`, `397`) and `retain` (`1733`, `1740`), plus moq-net's own +`ietf/parameters.rs:14,40` num_enum tag reads, which are also byte reinterpretation and +stay refused. + +Public API: the translator's output. diff --git a/quest/m3/ts-unsafe/unchecked-ops.md b/quest/m3/ts-unsafe/unchecked-ops.md new file mode 100644 index 000000000..24a29dc75 --- /dev/null +++ b/quest/m3/ts-unsafe/unchecked-ops.md @@ -0,0 +1,16 @@ +# [XS] Unchecked integer operations + +## Goal + +[rust2-ts](/ts/README.md) translates the integers' `unchecked_add`, `unchecked_sub`, +`unchecked_mul`, `unchecked_neg`, `unchecked_shl`, `unchecked_shr`, +`unchecked_funnel_shl`, and `unchecked_funnel_shr`, and `NonZero::new_unchecked`, proven +by differential tests against native Rust. Today each is refused as an unsupported call. + +## Plan + +Each is its checked form where Rust defines it. A violated precondition is UB, so per +[Throw only where Rust panics](/quest/m3/ts-runtime-throws.md) it aborts worded like std's +`unsafe precondition(s) violated` check, as a debug build does. No consumer at the moq pin. + +Public API: the translator's output. diff --git a/quest/m3/ts-unsafe/unsafecell.md b/quest/m3/ts-unsafe/unsafecell.md new file mode 100644 index 000000000..33c4225cd --- /dev/null +++ b/quest/m3/ts-unsafe/unsafecell.md @@ -0,0 +1,15 @@ +# [XS] UnsafeCell's raw accessors + +## Goal + +[rust2-ts](/ts/README.md) translates `UnsafeCell::raw_get`, `UnsafeCell::from_mut`, and +`UnsafeCell::replace`, proven by differential tests against native Rust. Today each is +refused as an unsupported call, and `raw_get(&c)` also as a raw pointer behind a +reference. + +## Plan + +`UnsafeCell::get` already lends the cell where it is an `rs.Place`. No consumer at the moq +pin. + +Public API: the translator's output. diff --git a/quest/m3/ts-unsafe/vec-drain-mir.md b/quest/m3/ts-unsafe/vec-drain-mir.md new file mode 100644 index 000000000..29ae3ef5d --- /dev/null +++ b/quest/m3/ts-unsafe/vec-drain-mir.md @@ -0,0 +1,23 @@ +# [M] Vec::drain from std's MIR + +## Goal + +[rust2-ts](/ts/README.md) translates `Vec::drain` from std's MIR, whose `Drain` reads its +range out and moves the tail back through raw pointers into the `Vec`'s storage, +replacing `rs.vecDrain` and `rs.byteVecDrain`, the runtime port that +[the slice gaps' PR](https://github.com/moq-dev/rust2/pull/259) mapped it to +(`ts/src/lower/library.rs`). The lang fixture's `slices::drains` differential case proves +it still matches std's items, drops, and what a forgotten drain leaves. + +## Plan + +moq calls it (`client.rs:821`, `priority.rs:216`) and the port works, so this is fidelity +to std rather than a blocker: the port must track std by hand. The B-tree port of +[#577](https://github.com/moq-dev/rust2/pull/577) is the precedent. Measure the TS +bench's drain-heavy workloads against the port. + +Public API: the translator's output. + +## Required + +- [A Vec's spare capacity](/quest/m3/ts-unsafe/vec-spare-capacity.md) - std's `Drain` calls `set_len` diff --git a/quest/m3/ts-unsafe/vec-from-raw-parts.md b/quest/m3/ts-unsafe/vec-from-raw-parts.md new file mode 100644 index 000000000..7e77b7ed5 --- /dev/null +++ b/quest/m3/ts-unsafe/vec-from-raw-parts.md @@ -0,0 +1,18 @@ +# [S] Vec::from_raw_parts of a typed allocation + +## Goal + +[rust2-ts](/ts/README.md) translates `Vec::from_raw_parts` of memory `alloc` gave for a +`Layout::array::(n)` (or an equal layout), so the `Vec` adopts that JS array of `T`s +with its length and capacity and drops its elements and frees it as std does, proven by +differential tests against native Rust that check length, capacity, and drops. Today it +is an unsupported call. + +## Plan + +Typed allocation is a fresh JS array of `T`s since the 2026-11-01 decision in +[the epic](/quest/m3/ts-unsafe/README.md), and `rs.Vec` holds std's capacity since +[#555](https://github.com/moq-dev/rust2/pull/555). smallvec spills its inline buffer this +way: 2 of its 10 model refusals at the moq pin, which moq drops or binds. + +Public API: the translator's output. diff --git a/quest/m3/ts-unsafe/vec-spare-capacity.md b/quest/m3/ts-unsafe/vec-spare-capacity.md new file mode 100644 index 000000000..0382cc9fe --- /dev/null +++ b/quest/m3/ts-unsafe/vec-spare-capacity.md @@ -0,0 +1,25 @@ +# [M] A Vec's spare capacity + +## Goal + +[rust2-ts](/ts/README.md) translates writes into a `Vec`'s spare capacity, through +`as_mut_ptr().add(len)` or `spare_capacity_mut`, committed by `set_len`, for any `T`, +proven by differential tests against native Rust. Today `set_len` is refused but for a +byte `Vec`'s (`unsupported call to std::vec::Vec::::set_len`, `ts/src/lower/body.rs`), +and a write past the length throws `a read or write through a pointer past the array JS +keeps it in` (`ts/runtime/rs/collections.ts`) where Rust defines it. + +## Plan + +`rs.Vec` holds std's capacity since [#555](https://github.com/moq-dev/rust2/pull/555); +the spare slots need to live outside the array's length until `set_len` takes them, as a +byte `Vec`'s do since [#568](https://github.com/moq-dev/rust2/pull/568). Removing the +run-time throw is also a gap of [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md). +Copying a `MaybeUninit` slot never written is defined Rust and must translate, preserving +it as Rust does; only a read that produces an invalid `T` may abort, as a precondition +abort. + +No consumer at the moq pin. [Vec::drain](/quest/m3/ts-unsafe/vec-drain-mir.md) and +[BinaryHeap](/quest/m3/ts-unsafe/binary-heap-mir.md) from std's MIR need it. + +Public API: the translator's output. diff --git a/quest/m4/go/coverage.md b/quest/m4/go/coverage.md index 261cb796c..37af9cadb 100644 --- a/quest/m4/go/coverage.md +++ b/quest/m4/go/coverage.md @@ -25,3 +25,17 @@ The spike refuses each of these with its span (`go/fixtures/reject/expected.txt` The translation targets the host, 64-bit `usize`; a 32-bit Go target needs its own translation. + +Decided with the maintainer on 2026-11-06: coverage waits on [speed](/quest/m4/go/speed.md), +as the epic orders them, and splits into one quest per construct above only once speed +reaches its bar. A failed speed result deletes `go/`, so planning the constructs earlier +would be wasted. The last coverage child reruns `just go bench` against speed's bar, since +constructs such as `Vec::capacity` change what the stream workload runs. + +- ✅ Gate on speed, split later. +- Split now, each child requiring speed. +- Split now, ungated. + +## Required + +- [Match cgo on the crate](/quest/m4/go/speed.md) - the translated crate within 2x of cgo on a group's stream end to end diff --git a/ts/README.md b/ts/README.md index 3a9cb0857..d0ab6b831 100644 --- a/ts/README.md +++ b/ts/README.md @@ -1074,7 +1074,7 @@ they need. ## Not yet Byte reinterpretation, pointer addresses, pointer arithmetic on a value JS keeps as an object (other than wrapping arithmetic on a null or dangling pointer), memory from `alloc` other than an array of values JS copies, and unions whose -fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than +fields overlap in different types ([unsafe](/quest/m3/ts-unsafe/README.md)), and trait objects other than `dyn Debug`, `dyn Display`, `dyn Future`, `dyn Any`, `dyn Error`, `dyn Fn*`, `dyn Buf`, `dyn Iterator` and its subtraits, and the translated crates' traits' behind `&`, `&mut`, `Box`, `Arc`, `Rc`, `Weak`, or `Pin` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `Box` made `From` a string, binds to hand-written TS ([bindings](#bindings)).