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
5 changes: 4 additions & 1 deletion quest/m3/ts-runtime-throws.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ The rest are gaps. At `main` they include a panic its hook cannot locate
[#565](https://github.com/moq-dev/rust2/pull/565)), `offset_from` of one Rust allocation
JS keeps in two arrays, a pointer moved past the array JS keeps it in (`AtIndex.reach`, such
as one 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.
values, ranges stepped without their type, fmt widths written as a placeholder, and a raw
slice of a byte in no array cast to `*const [u8; N]` by a route the body doesn't show,
which [a byte local in a buffer of its own](/quest/m3/ts-unsafe/lone-byte-buffers.md) owns
for locals.
`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, where
JS may make two `rs.Place`s for one place, are out of scope: addresses were their route, and
Expand Down
2 changes: 1 addition & 1 deletion quest/m3/ts-unsafe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ Public API: the translator's output.
- [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
- [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
- [NonZero's unchecked arithmetic](/quest/m3/ts-unsafe/nonzero-unchecked-ops.md) - `NonZero::unchecked_add` and `unchecked_mul` translate, aborting at std's site
- [UnsafeCell's raw accessors](/quest/m3/ts-unsafe/unsafecell.md) - `UnsafeCell::raw_get`, `from_mut`, and `replace` translate
- [A Box of one uninitialized value](/quest/m3/ts-unsafe/box-new-uninit.md) - `Box::new_uninit` and `new_zeroed` for one value translate, through `write` and `assume_init`
- [A byte local whose address is taken, in a buffer of its own](/quest/m3/ts-unsafe/lone-byte-buffers.md) - raw and mutable slices of a `u8` local, and casts of them to byte arrays, translate instead of being refused
18 changes: 0 additions & 18 deletions quest/m3/ts-unsafe/array-casts.md

This file was deleted.

32 changes: 32 additions & 0 deletions quest/m3/ts-unsafe/lone-byte-buffers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# [M] A byte local whose address is taken, in a buffer of its own

## Goal

[rust2-ts](/ts/README.md) keeps a `u8` local whose raw pointer the body takes (`&raw const`
or `&raw mut` of it) in a `Uint8Array(1)`, so raw slices, mutable slices, and casts to
`*const [u8; N]` of it are views of that buffer, as for any other byte, proven by
differential tests against native Rust. Today JS keeps such a byte in no array of bytes,
so `stray` and `lone_byte_array` (`ts/src/lower/mir/origin.rs`) refuse, with `a raw or
mutable slice of a byte in no array of bytes`, each route the body shows, and the runtime
throws the same where it doesn't (`ptrByteSlice`, `RawSlice.byteArray` in
`ts/runtime/rs.ts`), or copies the byte for a shared slice (`Array::Copy`).

## Plan

Decided with the maintainer on 2026-11-07: m3. Planned on review of
[#624](https://github.com/moq-dev/rust2/pull/624), whose body-visible refusal of a cast of
an empty raw slice of a lone byte to `*const [u8; N]` kept missing routes (`&raw *s`,
`NonNull::from`, `NonNull::new(p).unwrap()`); this fixes the class at its root.

- Which locals: those whose `&raw` the body takes, decided per body before lowering, so
every read and write of the local is `buf[0]` and a place of it is the `rs.AtIndex` of
element 0.
- Then the lone-byte refusals and the `Array::Copy` case go for those locals. A byte in a
field of an object JS keeps, or one behind a reference from elsewhere, stays in no array:
keep refusing or throwing there, and say so in the README's pointer row.

Public API: the translator's output.

## Related

- [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md) - lists the run-time throws this removes for locals
5 changes: 2 additions & 3 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re
| `Vec<T>` | `rs.Vec<T>`: its items, the array `items`, and the capacity std's holds; `rs.vecFrom(items)` makes one of an array with room for exactly them. `rs.ByteVec` of bytes |
| `Cow<[T]>`, `Cow<str>` | `rs.Cow<T>`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows; `rs.ByteCow` for bytes, an `rs.ByteVec` or an `rs.ByteSlice`; `rs.StrCow` for text, the `rs.String` it owns or an `rs.Borrowed` holding the string it borrows, whose text `rs.strCowStr` reads |
| `&T`, `&mut T`, `Box<T>`, `Reverse<T>`, `NonZero<T>`, `AssertUnwindSafe<T>` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rs.Place<T>`, which for what a box holds is the place holding the box |
| `*const T`, `*mut T`, `NonNull<T>` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place<T>` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, or copy the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rs.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rs.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rs.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rs` class such as `rs.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place |
| `*const T`, `*mut T`, `NonNull<T>` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place<T>` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, or copy the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. A pointer to a slice cast to one to an array of its elements is the `rs.Slice` or `rs.ByteSlice` of that many from its first, read through only where Rust reads. A `*const ()` is an identity, such as a waker's data, and `rs.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rs.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rs.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rs` class such as `rs.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place |
| `MaybeUninit<T>` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined`, as each of `Box::new_uninit_slice`'s is; a slice's `write_clone_of_slice` clones into its slots without dropping what they held |
| `ManuallyDrop<T>` | `T`, which nothing drops but its own `drop`; its `Debug`, comparisons, and constants are refused |
| `union` | an object of slots named by offset, `at0` and `at8`, each holding the field part kept there; fields must overlap in the same type, or in a non-null pointer and an `Option` of it |
Expand Down Expand Up @@ -149,6 +149,7 @@ crate translation reaches is refused, and so are two versions of one crate it re
| `pin!(x)`, `poll_fn(f)`, `future::pending()`, `future::ready(v)` | `x`; `rs.PollFn`, which drops `f` with itself; `rs.NEVER`, which every `Pending` is; `rs.Ready`, whose first poll or `into_inner` takes `v` and a later one panics, and which drops `v` with itself unless one took it |
| `Box::pin(x)`, `dyn Future<Output = T>` | `x`; `rs.Future<T>`: an `async` body or std's future as it is, and another future as an `rs.DynFuture` over its `poll`, which drops it with itself unless borrowed |
| `bytes::Bytes` | `rs.ByteSlice`, as `&[u8]` is: its clones and slices share bytes that never change |
| `io::IoSlice` | `rs.ByteSlice`, the `&[u8]` it lends, which `advance` and `advance_slices` move past |
| `bytes::BytesMut` | `rs.BytesMut`: a window of storage, which what it splits off and freezes share without overlapping |
| `rand::rngs::ThreadRng` | `rs.ThreadRng`, which holds no state |
| `hashbrown::HashMap`, `HashSet` | `rs.HashMap<K, V>` and `rs.HashSet<K>`, as std's, which are hashbrown's |
Expand Down Expand Up @@ -648,8 +649,6 @@ not all of `Buf`'s; a TS caller sees the same in its type. Hashing into a `dyn H
each of `Hasher`'s. A class of a std type's value calls the mapping of its impl, such as
`DefaultHasher`'s `Hasher`, or a `String`'s or a `Formatter`'s `fmt::Write`, and one no
mapping makes, such as a `Vec<u8>`'s `io::Write`, is refused where a call dispatches to it.
A `dyn Buf` holds no `chunks_vectored`, whose `IoSlice` has no JS value, so a call of it
fails.

A `dyn Any` is such an object with no methods, whose class `is`, `downcast_ref`,
`downcast_mut`, and `Box`'s, `Arc`'s, and `Rc`'s `downcast` check for: `a instanceof of$dyns_Square ?
Expand Down
Loading