Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
21 changes: 18 additions & 3 deletions quest/m2/ts-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ The codec's differential harness is the pattern to repeat. The shared npm runtim
(for checked integer ops and the rest of `rs.ts`) is published only once two
generated packages need it.

✅ Decided with the maintainer on 2026-11-04, for any crate: a pointer's address read as an
integer is an id unique among live objects, a multiple of the type's alignment, and whatever
has no exact answer, such as a pointer made back from an address, is refused at translation.

✅ Decided with the maintainer on 2026-11-06, refining that: only an `Arc`'s or `Rc`'s value
has an address. Its id belongs to the `rs.Arc` allocation and is freed for reuse when its
strong and weak counts reach 0, exactly as Rust frees it. Reading the address of a type the
program keeps anywhere else (a borrowed local, a field, a `Box`, a collection, a static), and
moving one out of its `Arc` (`into_inner`, `try_unwrap`, `unwrap_or_clone`, `make_mut`, a
whole write through `get_mut`), is refused with a span. Rejected: freeing an id when JS
collects its object, since GC never runs within one synchronous job, so churning
allocations could exhaust wasm32's 4 GiB where Rust reuses the freed memory; freeing at each
type's drop, which changes drop emission and still refuses values never dropped; and a
counter that never reuses, since `usize` is 32 bits on wasm32, so a long-running program
would exhaust the address space where Rust reuses freed blocks.

Decided with the maintainer on 2026-10-23, for the subset: `u128` stays, on the
[wide-integer](/ts/README.md#design) representation.

Expand All @@ -45,12 +61,12 @@ Decided with the maintainer on 2026-10-27, from a census of the model's 22 publi
- The end-to-end work stays here: the model's roots, its differential harness, and the
`frames.ts` workload. moq writes the binding files `model.toml` names, in its own tree.

The census at the pin on 2026-11-06: 73 refusals. `Timestamp` alone refuses nothing, and
The census at the pin on 2026-11-06: 66 refusals. `Timestamp` alone refuses nothing, and
the [model fixture](/ts/README.md#tests)'s harness runs it against native moq-net at the
pin; each case it adds as a type translates. Every other root reaches the rest of the
model, so each child below unblocks all of them together. The rest is moq's, for its
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
crossbeam-epoch, whose tagged pointers and inline closures (30 refusals) translation
refuses for good, and smallvec's union over a type parameter and raw parts (10) wait on
[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`.
Expand All @@ -63,7 +79,6 @@ kept separate since each lands alone; rejected: one quest for `ArrayString`.
## Required

- [Bytes from an owner that drops](/quest/m2/ts-model/bytes-owner.md) - `Bytes::from_owner` of a frame's segment
- [Addresses as identity](/quest/m2/ts-model/addresses.md) - `Arc::as_ptr(a) as usize` as a key, which needs a decision
- [Option fields at a unit instance](/quest/m2/ts-model/unit-option-fields.md) - `Idle<()>`'s `Option<T>` field tells `Some(())` from `None`
- [panic! in a 2015 or 2018 crate](/quest/m2/ts-model/begin-panic.md) - the `begin_panic` mapping keyed by its defining module
- [A string's bytes through its pointer](/quest/m2/ts-model/str-as-ptr.md) - `str::as_ptr` reads the string's UTF-8
Expand Down
24 changes: 0 additions & 24 deletions quest/m2/ts-model/addresses.md

This file was deleted.

2 changes: 1 addition & 1 deletion quest/m3/ts-runtime-throws.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ owns, or a pointer kept in a field), `Bytes::slice_ref` of other storage, `alloc
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
in scope too, through [addresses](/quest/m2/ts-model/addresses.md)' identities.
in scope too.

Public API: the translator's output, and which crates translate.

Expand Down
3 changes: 2 additions & 1 deletion ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re
| `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 string it owns or an `rs.Borrowed` holding the one it borrows, which `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`, a `String`, 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, 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 |
| a raw pointer's address: `p as usize`, `addr`, `expose_provenance`, and a raw pointer's `Hash` and `Ord` | `rs.addr`: null's 0, a dangling pointer's alignment, `Weak::new`'s `usize::MAX`, and otherwise the address of the `Arc` or `Rc` value it points at, as an allocator places the allocation: after its counts, at a multiple of the value's alignment, in a block no other live allocation's overlaps, freed for reuse once its strong and weak counts reach 0, as Rust frees it. Only a value an `Arc` or `Rc` holds itself has one: an address of a type the program keeps anywhere else, such as in a local it borrows, drops, or gives std, a field, a `Box`, a collection, a static, or what JS is given or gives the crate whole, or writes whole in place, as through `get_mut`, is refused, as are moving one out of its `Arc` by `into_inner` or `make_mut` (`try_unwrap` and `unwrap_or_clone` translate for no type), `Weak::as_ptr` of one `Arc::new_cyclic` makes, and a pointer made of an address |
| `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 @@ -1079,7 +1080,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
Byte reinterpretation, pointers made of 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/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<dyn Error>` made `From` a string, binds to
Expand Down
30 changes: 16 additions & 14 deletions ts/fixtures/census/census.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
| Count | Construct | First |
| ---: | --- | --- |
| 3 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | fixtures/census/src/lib.rs:9:2 |
| 2 | a cast from `_` to `_`, between a pointer and an integer | fixtures/census/src/lib.rs:9:2 |
| 2 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | fixtures/census/src/lib.rs:19:12 |
| 2 | a mutable static, which any code may write | fixtures/census/src/lib.rs:38:3 |
| 1 | `Bytes::from_owner` of `_`, which would drop with the last clone of what it makes, which is not supported | fixtures/census/src/lib.rs:45:2 |
| 1 | `Pointer` formatting, as in `{:p}`, is not supported | fixtures/census/src/lib.rs:24:2 |
| 1 | a pointer made of an address, which JS has no memory at | fixtures/census/src/lib.rs:14:2 |
| 1 | the address of a pointer to `_`, each a place JS makes of its own | fixtures/census/src/lib.rs:9:2 |
| 1 | union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart | fixtures/census/src/lib.rs:28:1 |

## By API
Expand All @@ -22,12 +23,9 @@ The type or module each refused type or path belongs to.

| Count | API | First |
| ---: | --- | --- |
| 4 | `u32` | fixtures/census/src/lib.rs:9:2 |
| 5 | `u32` | fixtures/census/src/lib.rs:9:2 |
| 2 | `u64` | fixtures/census/src/lib.rs:19:47 |
| 2 | `u8` | fixtures/census/src/lib.rs:19:12 |
| 2 | `usize` | fixtures/census/src/lib.rs:9:2 |
| 1 | `*const T` | fixtures/census/src/lib.rs:14:2 |
| 1 | `*const u32` | fixtures/census/src/lib.rs:9:2 |
| 1 | `Bits` | fixtures/census/src/lib.rs:28:1 |
| 1 | `std::sync::Arc` | fixtures/census/src/lib.rs:45:2 |

Expand All @@ -37,11 +35,10 @@ The type or module each refused type or path belongs to.
| ---: | --- | --- | --- |
| 2 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | `u32` | fixtures/census/src/lib.rs:9:2 |
| 1 | `Bytes::from_owner` of `_`, which would drop with the last clone of what it makes, which is not supported | `std::sync::Arc` | fixtures/census/src/lib.rs:45:2 |
| 1 | a cast from `_` to `_`, between a pointer and an integer | `*const T`, `usize` | fixtures/census/src/lib.rs:14:2 |
| 1 | a cast from `_` to `_`, between a pointer and an integer | `*const u32`, `usize` | fixtures/census/src/lib.rs:9:2 |
| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `u8` | fixtures/census/src/lib.rs:19:12 |
| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u64`, `u8` | fixtures/census/src/lib.rs:19:47 |
| 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | `u64` | fixtures/census/src/lib.rs:19:48 |
| 1 | the address of a pointer to `_`, each a place JS makes of its own | `u32` | fixtures/census/src/lib.rs:9:2 |
| 1 | union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart | `Bits`, `u32` | fixtures/census/src/lib.rs:28:1 |

## Spans
Expand All @@ -54,13 +51,6 @@ The type or module each refused type or path belongs to.

</details>

<details><summary>a cast from `_` to `_`, between a pointer and an integer (2)</summary>

- fixtures/census/src/lib.rs:9:2: a cast from `*const u32` to `usize`, between a pointer and an integer
- fixtures/census/src/lib.rs:14:2: a cast from `*const T` to `usize`, between a pointer and an integer

</details>

<details><summary>a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another (2)</summary>

- fixtures/census/src/lib.rs:19:12: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another
Expand All @@ -87,6 +77,18 @@ The type or module each refused type or path belongs to.

</details>

<details><summary>a pointer made of an address, which JS has no memory at (1)</summary>

- fixtures/census/src/lib.rs:14:2: a pointer made of an address, which JS has no memory at

</details>

<details><summary>the address of a pointer to `_`, each a place JS makes of its own (1)</summary>

- fixtures/census/src/lib.rs:9:2: the address of a pointer to `u32`, each a place JS makes of its own

</details>

<details><summary>union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart (1)</summary>

- fixtures/census/src/lib.rs:28:1: union `Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart
Expand Down
8 changes: 4 additions & 4 deletions ts/fixtures/census/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

#![allow(dead_code, reason = "each item is a census root, not called from Rust")]

/// A pointer's address, which JS has no counterpart of.
/// The address of a place, which JS makes anew each borrow.
pub fn addressed(n: u32) -> usize {
&n as *const u32 as usize
}

/// A pointer's address in a body no instance reaches, lowered over `T`.
pub fn generic<T>(t: T) -> usize {
&t as *const T as usize
/// A pointer made of an address in a body no instance reaches, lowered over `T`.
pub fn generic<T>(at: usize) -> *const T {
std::ptr::without_provenance(at)
}

/// A pointer read as another type's bytes, at two types.
Expand Down
8 changes: 8 additions & 0 deletions ts/fixtures/lang/pointers/expected/core/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import * as rs from "../rs.ts";
import { copy$pointers_Pair, copy$pointers_Spot } from "../lib.ts";
import type * as Keyed from "../pointers.Keyed.ts";
import type * as Loaded from "../pointers.Loaded.ts";
import type * as Pair from "../pointers.Pair.ts";
import type * as Spot from "../pointers.Spot.ts";
Expand All @@ -13,6 +14,13 @@ export function isSomeAnd$1s2u5lz(self: rs.u8 | undefined, f: (a0: rs.u8) => boo
return f(self);
}

export function isSomeAnd$0fp5rqa(self: rs.Arc<Keyed.Keyed> | undefined, f: (a0: rs.Arc<Keyed.Keyed>) => boolean): boolean {
if (self === undefined) {
return false;
}
return f(self);
}

export function isSomeAnd$1nosv3e(self: [rs.Arc<[rs.u16, rs.u8] | undefined>, rs.Arc<Spot.Spot>] | undefined, f: (a0: [rs.Arc<[rs.u16, rs.u8] | undefined>, rs.Arc<Spot.Spot>]) => boolean): boolean {
if (self === undefined) {
return false;
Expand Down
13 changes: 10 additions & 3 deletions ts/fixtures/lang/pointers/expected/lib.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Generated by rust2-ts. Do not edit.

import * as rs from "./rs.ts";
import { lists, shared, places, unchecked, nested, copies, sentinels, cells, rebound, lapsed, writers, made, segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken, bytes, loaded, reattached, partWritten, aimed, distinct, rawArcs, rawPlaces, arrays } from "./pointers.ts";
import { lists, shared, places, unchecked, nested, copies, sentinels, cells, rebound, lapsed, writers, made, segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken, bytes, loaded, reattached, partWritten, aimed, distinct, rawArcs, rawPlaces, addresses, arrays } from "./pointers.ts";
import type * as Aimed from "./pointers.Aimed.ts";
import * as Bin from "./pointers.Bin.ts";
import type * as ByCall from "./pointers.ByCall.ts";
Expand All @@ -11,6 +11,7 @@ import type * as Duo from "./pointers.Duo.ts";
import type * as Halves from "./pointers.Halves.ts";
import type * as Housed from "./pointers.Housed.ts";
import type * as Inner from "./pointers.Inner.ts";
import type * as Keyed from "./pointers.Keyed.ts";
import type * as Leaning from "./pointers.Leaning.ts";
import * as List from "./pointers.List.ts";
import type * as Outer from "./pointers.Outer.ts";
Expand Down Expand Up @@ -201,6 +202,10 @@ export function drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin(_1: [rs.Arc<
_1[1][Symbol.dispose]();
}

export function drop$tuple2_sync_Weak_pointers_Keyed_usize(_1: [rs.Weak<Keyed.Keyed>, rs.usize]): void {
_1[0][Symbol.dispose]();
}

export function drop$tuple2_u8_sync_Arc_u8(_1: [rs.u8, rs.Arc<rs.u8>]): void {
_1[1][Symbol.dispose]();
}
Expand Down Expand Up @@ -287,8 +292,10 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By
rawArcs(data25, out25);
}] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::raw_places", (_235: rs.u8, data26: rs.ByteSlice, out26: rs.ByteVec): undefined => {
rawPlaces(data26, out26);
}] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::arrays", (_236: rs.u8, data27: rs.ByteSlice, out27: rs.ByteVec): undefined => {
arrays(data27, out27);
}] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::addresses", (_236: rs.u8, data27: rs.ByteSlice, out27: rs.ByteVec): undefined => {
addresses(data27, out27);
}] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::arrays", (_237: rs.u8, data28: rs.ByteSlice, out28: rs.ByteVec): undefined => {
arrays(data28, out28);
}] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]);
}

Expand Down
Loading