Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions quest/m2/ts-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions quest/m3/ts-runtime-throws.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
83 changes: 0 additions & 83 deletions quest/m3/ts-unsafe.md

This file was deleted.

80 changes: 80 additions & 0 deletions quest/m3/ts-unsafe/README.md
Original file line number Diff line number Diff line change
@@ -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::<T>(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<T>`
- [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
18 changes: 18 additions & 0 deletions quest/m3/ts-unsafe/array-casts.md
Original file line number Diff line number Diff line change
@@ -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.
21 changes: 21 additions & 0 deletions quest/m3/ts-unsafe/binary-heap-mir.md
Original file line number Diff line number Diff line change
@@ -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`
18 changes: 18 additions & 0 deletions quest/m3/ts-unsafe/generic-unions.md
Original file line number Diff line number Diff line change
@@ -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.
23 changes: 23 additions & 0 deletions quest/m3/ts-unsafe/raw-behind-ref.md
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions quest/m3/ts-unsafe/unchecked-ops.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions quest/m3/ts-unsafe/unsafecell.md
Original file line number Diff line number Diff line change
@@ -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.
Loading