From 77de6f0a27f24234224b90b4cabb9adad95cabde Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 16:41:31 -0800 Subject: [PATCH 01/29] chore(quest): claim quest/m3/ts-unsafe/raw-behind-ref.md Quest-Claim: 4a3efcd012476046 From 3478933bbe33d5efc029a6585612a612fad31710 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 18:22:39 -0800 Subject: [PATCH 02/29] feat(ts): translate a raw pointer behind a reference A pointer made from a reference is where the body shows the reference borrowed, and else a place of its own: of a shared reference's value, or writing a whole value into the object a &mut is in place. Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-unsafe/README.md | 1 - quest/m3/ts-unsafe/raw-behind-ref.md | 23 -- ts/README.md | 2 +- ts/fixtures/census/census.md | 17 +- .../lang/conversions/expected/conversions.ts | 14 +- .../lang/pointers/expected/core/bool.ts | 7 + .../lang/pointers/expected/core/option.ts | 35 ++ ts/fixtures/lang/pointers/expected/lib.ts | 4 +- .../pointers/expected/pointers.Drained.ts | 62 ++++ .../lang/pointers/expected/pointers.ts | 289 ++++++++++++++- ts/fixtures/lang/pointers/src/lib.rs | 1 + ts/fixtures/lang/pointers/src/pointers.rs | 153 ++++++++ ts/fixtures/reject/census.txt | 2 +- ts/fixtures/reject/expected.txt | 2 +- ts/fixtures/reject/src/alloc.rs | 8 +- ts/fixtures/reject/ts.toml | 2 +- ts/src/lower/mir/call.rs | 11 + ts/src/lower/mir/origin.rs | 110 ++++-- ts/src/lower/mir/place.rs | 24 +- ts/src/lower/mir/rvalue.rs | 332 ++++++++++++------ ts/src/lower/pointer.rs | 13 +- 21 files changed, 914 insertions(+), 198 deletions(-) delete mode 100644 quest/m3/ts-unsafe/raw-behind-ref.md create mode 100644 ts/fixtures/lang/pointers/expected/pointers.Drained.ts diff --git a/quest/m3/ts-unsafe/README.md b/quest/m3/ts-unsafe/README.md index fbe362cfd..28a7da1ba 100644 --- a/quest/m3/ts-unsafe/README.md +++ b/quest/m3/ts-unsafe/README.md @@ -70,7 +70,6 @@ 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 diff --git a/quest/m3/ts-unsafe/raw-behind-ref.md b/quest/m3/ts-unsafe/raw-behind-ref.md deleted file mode 100644 index 9fecd0d2e..000000000 --- a/quest/m3/ts-unsafe/raw-behind-ref.md +++ /dev/null @@ -1,23 +0,0 @@ -# [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/ts/README.md b/ts/README.md index d7717fd16..189d795e6 100644 --- a/ts/README.md +++ b/ts/README.md @@ -109,7 +109,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: 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` | `rs.Cow`: 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`, `Reverse`, `NonZero`, `AssertUnwindSafe` | 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`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | 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` 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 | +| `*const T`, `*mut T`, `NonNull` | 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` 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or writing a whole value into the object a `&mut` is in place. 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`: `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`, 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 | diff --git a/ts/fixtures/census/census.md b/ts/fixtures/census/census.md index e7587fd63..633cc4d92 100644 --- a/ts/fixtures/census/census.md +++ b/ts/fixtures/census/census.md @@ -1,6 +1,6 @@ # Census of `census-fixture` -- Refusals: 12 +- Refusals: 9 - Function instances: 6 - Generic functions no instance reached, lowered over their type parameters: 1 @@ -8,7 +8,6 @@ | 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 | @@ -22,38 +21,28 @@ The type or module each refused type or path belongs to. | Count | API | First | | ---: | --- | --- | -| 4 | `u32` | fixtures/census/src/lib.rs:9:2 | -| 2 | `u64` | fixtures/census/src/lib.rs:19:47 | +| 2 | `u32` | fixtures/census/src/lib.rs:19:12 | | 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 | +| 1 | `u64` | fixtures/census/src/lib.rs:19:47 | ## By type or path | Count | Construct | Type or path | First | | ---: | --- | --- | --- | -| 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 | union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart | `Bits`, `u32` | fixtures/census/src/lib.rs:28:1 | ## Spans -
a raw pointer to `_` behind a reference, which is the object and not where it is kept (3) - -- fixtures/census/src/lib.rs:9:2: a raw pointer to `u32` behind a reference, which is the object and not where it is kept -- fixtures/census/src/lib.rs:19:13: a raw pointer to `u32` behind a reference, which is the object and not where it is kept -- fixtures/census/src/lib.rs:19:48: a raw pointer to `u64` behind a reference, which is the object and not where it is kept - -
-
a cast from `_` to `_`, between a pointer and an integer (2) - fixtures/census/src/lib.rs:9:2: a cast from `*const u32` to `usize`, between a pointer and an integer diff --git a/ts/fixtures/lang/conversions/expected/conversions.ts b/ts/fixtures/lang/conversions/expected/conversions.ts index 6059901f0..d6504be04 100644 --- a/ts/fixtures/lang/conversions/expected/conversions.ts +++ b/ts/fixtures/lang/conversions/expected/conversions.ts @@ -2100,7 +2100,7 @@ export function converted$Option_u16(data: rs.ByteSlice, out: rs.ByteVec): void put$conversions_Value_Option_u16(_199, out); } boxed$Option_u16(data, out); - put$conversions_Value_Option_u16({ v: b }.v, out); + put$conversions_Value_Option_u16(b, out); c = make$conversions_Value_Option_u16(data, 16); put$conversions_Value_Option_u16(c, out); } @@ -2187,7 +2187,7 @@ export function converted$String(data: rs.ByteSlice, out: rs.ByteVec): void { put$conversions_Value_String(_199, out); } boxed$String(data, out); - put$conversions_Value_String({ v: b }.v, out); + put$conversions_Value_String(b, out); c = make$conversions_Value_String(data, 16); put$conversions_Value_String(c, out); } @@ -2275,7 +2275,7 @@ export function converted$Vec_u32(data: rs.ByteSlice, out: rs.ByteVec): void { put$conversions_Value_Vec_u32(_199, out); } boxed$Vec_u32(data, out); - put$conversions_Value_Vec_u32({ v: b }.v, out); + put$conversions_Value_Vec_u32(b, out); c = make$conversions_Value_Vec_u32(data, 16); put$conversions_Value_Vec_u32(c, out); } @@ -2363,7 +2363,7 @@ export function converted$Vec_u8(data: rs.ByteSlice, out: rs.ByteVec): void { put$conversions_Value_Vec_u8(_199, out); } boxed$Vec_u8(data, out); - put$conversions_Value_Vec_u8({ v: b }.v, out); + put$conversions_Value_Vec_u8(b, out); c = make$conversions_Value_Vec_u8(data, 16); put$conversions_Value_Vec_u8(c, out); } @@ -3198,7 +3198,7 @@ export function converted$u32(data: rs.ByteSlice, out: rs.ByteVec): void { put$conversions_Value_u32(_199, out); } boxed$u32(data, out); - put$conversions_Value_u32({ v: b }.v, out); + put$conversions_Value_u32(b, out); c = make$conversions_Value_u32(data, 16); put$conversions_Value_u32(c, out); } @@ -3285,7 +3285,7 @@ export function converted$u64(data: rs.ByteSlice, out: rs.ByteVec): void { put$conversions_Value_u64(_199, out); } boxed$u64(data, out); - put$conversions_Value_u64({ v: b }.v, out); + put$conversions_Value_u64(b, out); c = make$conversions_Value_u64(data, 16); put$conversions_Value_u64(c, out); } @@ -3370,7 +3370,7 @@ export function converted$u8(data: rs.ByteSlice, out: rs.ByteVec): void { put$conversions_Value_u8(_199, out); } boxed$u8(data, out); - put$conversions_Value_u8({ v: b }.v, out); + put$conversions_Value_u8(b, out); c = make$conversions_Value_u8(data, 16); put$conversions_Value_u8(c, out); } diff --git a/ts/fixtures/lang/pointers/expected/core/bool.ts b/ts/fixtures/lang/pointers/expected/core/bool.ts index e694d25bc..df98e8987 100644 --- a/ts/fixtures/lang/pointers/expected/core/bool.ts +++ b/ts/fixtures/lang/pointers/expected/core/bool.ts @@ -3,6 +3,13 @@ import * as rs from "../rs.ts"; import type * as Spot from "../pointers.Spot.ts"; +export function thenSome$1gvhopg(self: boolean, t: rs.u16): rs.u16 | undefined { + if (!self) { + return undefined; + } + return t; +} + export function then$0bgt67i(self: boolean, f: () => rs.Weak): rs.Weak | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/pointers/expected/core/option.ts b/ts/fixtures/lang/pointers/expected/core/option.ts index 8f6edbacb..9d27f5111 100644 --- a/ts/fixtures/lang/pointers/expected/core/option.ts +++ b/ts/fixtures/lang/pointers/expected/core/option.ts @@ -48,6 +48,27 @@ export function unwrapOr$1fr498u(self: rs.u8 | undefined, default_: rs.u8): rs.u return self; } +export function map$00buaky(self: rs.u16 | undefined | rs.None, f: (a0: rs.u16 | undefined) => rs.u16 | undefined): rs.u16 | undefined | rs.None { + if (self === rs.None) { + return rs.None; + } + return f(self); +} + +export function map$18tou5q(self: string | undefined, f: (a0: string) => string): string | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + +export function map$1h9tk16(self: rs.ByteVec | undefined, f: (a0: rs.ByteVec) => rs.ByteVec): rs.ByteVec | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$1cfrse5(self: Loaded.Loaded | undefined, f: (a0: Loaded.Loaded) => Loaded.Loaded): Loaded.Loaded | undefined { if (self === undefined) { return undefined; @@ -55,6 +76,13 @@ export function map$1cfrse5(self: Loaded.Loaded | undefined, f: (a0: Loaded.Load return f(self); } +export function map$070fw7m(self: rs.u32 | undefined, f: (a0: rs.u32) => rs.u32): rs.u32 | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function mapOr$078vnqn(self: Pair.Pair | undefined, default_: rs.u8, f: (a0: Pair.Pair) => rs.u8): rs.u8 { if (self === undefined) { return default_; @@ -76,6 +104,13 @@ export function mapOr$173j2gr(self: [rs.u8, rs.Arc] | undefined, default_ return f(self); } +export function mapOr$0smr0aj(self: rs.u16 | undefined, default_: Uint8Array, _: (a0: rs.u16) => Uint8Array): Uint8Array { + if (self === undefined) { + return default_; + } + return rs.u16ToBeBytes(self); +} + export function filter$0wvwd70(self: rs.u32 | undefined, predicate: (a0: rs.u32) => boolean): rs.u32 | undefined { if (self !== undefined) { const x: rs.u32 = self; diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index e38684560..f90105b1a 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -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, arrays, behindRefs } 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"; @@ -289,6 +289,8 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By 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::behind_refs", (_237: rs.u8, data28: rs.ByteSlice, out28: rs.ByteVec): undefined => { + behindRefs(data28, out28); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts new file mode 100644 index 000000000..b49b63d8b --- /dev/null +++ b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts @@ -0,0 +1,62 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { map$00buaky as option_map$00buaky, map$18tou5q as option_map$18tou5q, map$1h9tk16 as option_map$1h9tk16, map$070fw7m as option_map$070fw7m } from "./core/option.ts"; + +/** + * What smallvec's `Drain` is: the elements of a slice nothing will drop again, each read + * out through the reference its iterator lends. + */ +export interface Drained { + iter: rs.Iter>; +} + +export function next$iter_Iterator_Option_u16(self: Drained): rs.u16 | undefined | rs.Done { + return rs.iterFromOption(option_map$00buaky(rs.iterToOption((self.iter as rs.Iter).next(), rs.None), (r: rs.u16 | undefined): rs.u16 | undefined => { + return { v: r }.v; + }), rs.None); +} + +export function next$iter_Iterator_String(self: Drained): string | rs.Done { + return rs.iterFromOption(option_map$18tou5q(rs.iterToOption((self.iter as rs.Iter).next()), (r: string): string => { + return { v: r }.v; + })); +} + +export function next$iter_Iterator_Vec_u8(self: Drained): rs.ByteVec | rs.Done { + return rs.iterFromOption(option_map$1h9tk16(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.ByteVec): rs.ByteVec => { + return { v: r }.v; + })); +} + +export function next$iter_Iterator_u32(self: Drained): rs.u32 | rs.Done { + return rs.iterFromOption(option_map$070fw7m(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.u32): rs.u32 => { + return { v: r }.v; + })); +} + +export function nextBack$iter_DoubleEndedIterator_Option_u16(self: Drained): rs.u16 | undefined | rs.None { + return option_map$00buaky(rs.iterToOption((self.iter as rs.Iter).nextBack(), rs.None), (r: rs.u16 | undefined): rs.u16 | undefined => { + return { v: r }.v; + }); +} + +export function nextBack$iter_DoubleEndedIterator_String(self: Drained): string | undefined { + return option_map$18tou5q(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: string): string => { + return { v: r }.v; + }); +} + +export function nextBack$iter_DoubleEndedIterator_Vec_u8(self: Drained): rs.ByteVec | undefined { + return option_map$1h9tk16(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.ByteVec): rs.ByteVec => { + return { v: r }.v; + }); +} + +export function nextBack$iter_DoubleEndedIterator_u32(self: Drained): rs.u32 | undefined { + return option_map$070fw7m(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.u32): rs.u32 => { + return { v: r }.v; + }); +} + +//# sourceMappingURL=pointers.Drained.ts.map diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 27c5d87df..990b05a91 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -2,14 +2,15 @@ import * as rs from "./rs.ts"; import { copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_Twin, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; -import { then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; -import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$173j2gr as option_mapOr$173j2gr, filter$0wvwd70 } from "./core/option.ts"; +import { thenSome$1gvhopg, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; +import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; import type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; import type * as ByCall from "./pointers.ByCall.ts"; import * as ByClosure from "./pointers.ByClosure.ts"; import type * as ByObject from "./pointers.ByObject.ts"; import * as Counted from "./pointers.Counted.ts"; +import * as Drained from "./pointers.Drained.ts"; import type * as Duo from "./pointers.Duo.ts"; import type * as Enclosed from "./pointers.Enclosed.ts"; import type * as Gauge from "./pointers.Gauge.ts"; @@ -2980,4 +2981,288 @@ export function lent(n: rs.u8): rs.ByteVec { return o; } +/** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ +export function drained$Option_u16(v: rs.Vec, bits: rs.u8): rs.Vec { + let next!: rs.u16 | undefined | rs.None; + const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const out: rs.Vec = new rs.Vec(); + let i: rs.i32 = 0; + while (true) { + if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { + next = rs.iterToOption(Drained.next$iter_Iterator_Option_u16(iter), rs.None); + } else { + next = Drained.nextBack$iter_DoubleEndedIterator_Option_u16(iter); + } + if (next !== rs.None) { + out.items.push(next); + i = rs.i32Add(i, 1); + } else { + return out; + } + } +} + +/** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ +export function drained$String(v: rs.Vec, bits: rs.u8): rs.Vec { + let next!: string | undefined; + const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const out: rs.Vec = new rs.Vec(); + let i: rs.i32 = 0; + while (true) { + if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { + next = rs.iterToOption(Drained.next$iter_Iterator_String(iter)); + } else { + next = Drained.nextBack$iter_DoubleEndedIterator_String(iter); + } + if (next !== undefined) { + out.items.push(next); + i = rs.i32Add(i, 1); + } else { + return out; + } + } +} + +/** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ +export function drained$Vec_u8(v: rs.Vec, bits: rs.u8): rs.Vec { + let next!: rs.ByteVec | undefined; + const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const out: rs.Vec = new rs.Vec(); + let i: rs.i32 = 0; + while (true) { + if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { + next = rs.iterToOption(Drained.next$iter_Iterator_Vec_u8(iter)); + } else { + next = Drained.nextBack$iter_DoubleEndedIterator_Vec_u8(iter); + } + if (next !== undefined) { + out.items.push(next); + i = rs.i32Add(i, 1); + } else { + return out; + } + } +} + +/** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ +export function drained$u32(v: rs.Vec, bits: rs.u8): rs.Vec { + let next!: rs.u32 | undefined; + const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const out: rs.Vec = new rs.Vec(); + let i: rs.i32 = 0; + while (true) { + if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { + next = rs.iterToOption(Drained.next$iter_Iterator_u32(iter)); + } else { + next = Drained.nextBack$iter_DoubleEndedIterator_u32(iter); + } + if (next !== undefined) { + out.items.push(next); + i = rs.i32Add(i, 1); + } else { + return out; + } + } +} + +/** + * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then + * drops or moves to the front. + */ +export function retained$String_pointers_behind_refs_closure6(v: rs.Vec, keep: (a0: rs.Place) => boolean): void { + let _10!: rs.usize; + const len: rs.usize = v.items.length; + const p: rs.Place = rs.arrayAsPtr(v.items); + let write: rs.usize = 0; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = len; + while ((_10 = iterStart++) < iterEnd) { + const _15: rs.Place = rs.ptrAdd(p, _10); + if (keep(_15)) { + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); + write = rs.usizeAdd(write, 1); + } + } + rs.vecSplitOff(v, write); +} + +/** + * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then + * drops or moves to the front. + */ +export function retained$Vec_u8_pointers_behind_refs_closure8(v: rs.Vec, keep: (a0: rs.ByteVec) => boolean): void { + let _10!: rs.usize; + const len: rs.usize = v.items.length; + const p: rs.Place = rs.arrayAsPtr(v.items); + let write: rs.usize = 0; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = len; + while ((_10 = iterStart++) < iterEnd) { + const _15: rs.Place = rs.ptrAdd(p, _10); + if (keep(_15.v)) { + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); + write = rs.usizeAdd(write, 1); + } + } + rs.vecSplitOff(v, write); +} + +/** + * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then + * drops or moves to the front. + */ +export function retained$u32_pointers_behind_refs_closure4(v: rs.Vec, keep: (a0: rs.Place) => boolean): void { + let _10!: rs.usize; + const len: rs.usize = v.items.length; + const p: rs.Place = rs.arrayAsPtr(v.items); + let write: rs.usize = 0; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = len; + while ((_10 = iterStart++) < iterEnd) { + const _15: rs.Place = rs.ptrAdd(p, _10); + if (keep(_15)) { + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); + write = rs.usizeAdd(write, 1); + } + } + rs.vecSplitOff(v, write); +} + +/** The length of the `Vec` a pointer from a reference the body is given points at. */ +export function vecLen(v: rs.ByteVec): rs.usize { + return { v }.v.length; +} + +/** + * `with` written whole through a pointer from a `&mut` the body is given, made each way + * std makes one, which its caller's `Vec` holds after. + */ +export function refilled(v: rs.ByteVec, with_: rs.ByteVec, how: rs.u8): void { + let p!: rs.Place; + const _5: rs.u8 = rs.u8Rem(how, 4); + if (_5 === 0) { + const target4 = v; + p = rs.place(() => target4, (value4) => Object.assign(target4, value4)); + } else if (_5 === 1) { + const target3 = v; + p = rs.place(() => target3, (value3) => Object.assign(target3, value3)); + } else if (_5 === 2) { + const target2 = v; + p = rs.place(() => target2, (value2) => Object.assign(target2, value2)); + } else { + const target = v; + p = rs.place(() => target, (value) => Object.assign(target, value)); + } + p.v = with_; + rs.byteVecPush(p.v, how); +} + +/** + * The value a pointer from a shared reference the body is given points at, made each way + * std makes one. + */ +export function behind(r: rs.u32, how: rs.u8): rs.u32 { + let p!: rs.Place; + const _4: rs.u8 = rs.u8Rem(how, 3); + if (_4 === 0) { + p = { v: r }; + } else if (_4 === 1) { + p = { v: r }; + } else { + p = { v: r }; + } + return p.v; +} + +/** + * Raw pointers from references: smallvec's `Drain` and `retain`, over values JS copies, a + * library type, and an `Option`; pointers from a reference the body borrowed, which move + * within the array it borrowed from; and from references the body is given, to a number + * and to a `Vec`, read and written whole. + */ +export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { + let _112!: rs.usize; + const bits: rs.u8 = option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0); + const nums: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_2: rs.u8): rs.u32 => { + return rs.u32Mul(_2 as rs.u32, 3); + }), undefined, 4, true); + rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(drained$u32(rs.vecFrom(nums.items.slice()), bits).items), (_23: rs.u32): rs.u8 => { + return _23 & 0xff; + }), undefined, true); + const words: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b3: rs.u8): string => { + return rs.fmtToString((f: rs.Formatter) => rs.u8Display(b3, f)); + }), undefined, 12, true); + for (const _25 of drained$String(rs.vecFrom(words.items.slice()), bits).items) { + rs.iterExtendBytes(out, rs.strBytes(_25), undefined, true); + } + const runs: rs.Vec = rs.iterToVec(rs.iterMap(rs.byteSliceChunks(data, 3), (a0: rs.ByteSlice): rs.ByteVec => { + return rs.byteSliceToByteVec(a0); + }), undefined, 12, true); + for (const r of drained$Vec_u8(rs.vecFrom(runs.items.map((a02: rs.ByteVec): rs.ByteVec => { + return rs.byteVecFrom(rs.byteVecView(a02)); + })), bits).items) { + rs.byteVecPush(out, r.length & 0xff); + rs.iterExtendBytes(out, rs.byteVecIntoIter(r), undefined, true); + } + for (const _56 of drained$Option_u16(rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_24: rs.u8): rs.u16 | undefined => { + const b4: rs.u8 = _24; + return thenSome$1gvhopg(rs.u8Rem(b4, 3) !== 0, b4 as rs.u16); + }), undefined, 4, true), bits).items) { + rs.iterExtendBytes(out, rs.iterSlice(option_mapOr$0smr0aj(_56, Uint8Array.of(255, 255), (a03: rs.u16): Uint8Array => { + return rs.u16ToBeBytes(a03); + })), undefined, true); + } + const nums2: rs.Vec = nums; + retained$u32_pointers_behind_refs_closure4(nums2, (n2: rs.Place): boolean => { + n2.v = rs.u32Add(n2.v, 1); + return rs.u32Rem(n2.v, 2) === 0; + }); + rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(nums2.items), (_26: rs.u32): rs.u8 => { + return _26 & 0xff; + }), undefined, true); + const words2: rs.Vec = words; + retained$String_pointers_behind_refs_closure6(words2, (w2: rs.Place): boolean => { + w2.v = w2.v + "!"; + return rs.usizeRem(rs.stringLen(w2.v), 2) === 0; + }); + rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(words2.items), (w3: string): rs.Iter => { + return rs.strBytes(w3); + }, undefined, true), "next"); + const runs2: rs.Vec = runs; + retained$Vec_u8_pointers_behind_refs_closure8(runs2, (r2: rs.ByteVec): boolean => { + rs.byteVecPush(r2, 7); + return rs.u8Rem(r2.data[rs.idx(r2, 0)], 2) === 0; + }); + rs.iterExtendBytes(out, rs.iterFlatten(rs.iterSlice(runs2.items), (value: rs.ByteVec) => rs.iterSlice(rs.byteVecView(value)), true), "next"); + const nums3: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_27: rs.u8): rs.u32 => { + return _27 as rs.u32; + }), undefined, 4, true); + if (rs.sliceFrom(nums3.items).length >= 2) { + const first: rs.Place = rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 0)); + rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, rs.usizeSub(nums3.items.length, 1))), first) & 0xff); + const _109: rs.usize = nums3.items.length; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = _109; + while ((_112 = iterStart++) < iterEnd) { + rs.byteVecPush(out, rs.ptrAdd(first, _112).v & 0xff); + } + rs.byteVecPush(out, rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v & 0xff); + rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v = 9; + rs.byteVecPush(out, nums3.items[rs.idx(nums3.items, 0)] & 0xff); + } + const v: rs.ByteVec = rs.byteSliceToByteVec(data); + rs.byteVecPush(out, vecLen(v) & 0xff); + const _143: rs.ByteVec = rs.iterToByteVec(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), undefined, true); + refilled(v, _143, bits); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); + const iter5: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); + while (true) { + const _153: [rs.usize, rs.u8] | rs.Done = iter5.next(); + if (_153 === rs.DONE) { + break; + } + rs.byteVecPush(out, behind(_153[1] as rs.u32, _153[0] & 0xff) & 0xff); + } +} + //# sourceMappingURL=pointers.ts.map diff --git a/ts/fixtures/lang/pointers/src/lib.rs b/ts/fixtures/lang/pointers/src/lib.rs index 28baff2b1..ef925469f 100644 --- a/ts/fixtures/lang/pointers/src/lib.rs +++ b/ts/fixtures/lang/pointers/src/lib.rs @@ -49,5 +49,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("pointers::raw_arcs", |_, data, out| pointers::raw_arcs(data, out)), ("pointers::raw_places", |_, data, out| pointers::raw_places(data, out)), ("pointers::arrays", |_, data, out| pointers::arrays(data, out)), + ("pointers::behind_refs", |_, data, out| pointers::behind_refs(data, out)), ] } diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index 10bfc8ffd..c4428c980 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2409,3 +2409,156 @@ fn lent(n: u8) -> Vec { struct Holder { arr: [u32; 2], } + +/// What smallvec's `Drain` is: the elements of a slice nothing will drop again, each read +/// out through the reference its iterator lends. +struct Drained<'a, T> { + iter: std::slice::Iter<'a, T>, +} + +impl Iterator for Drained<'_, T> { + type Item = T; + + fn next(&mut self) -> Option { + self.iter.next().map(|r| unsafe { ptr::read(r) }) + } +} + +impl DoubleEndedIterator for Drained<'_, T> { + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|r| unsafe { ptr::read(r) }) + } +} + +/// `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. +fn drained(v: Vec, bits: u8) -> Vec { + let v = mem::ManuallyDrop::new(v); + let mut iter = Drained { iter: v.iter() }; + let mut out = Vec::new(); + let mut i = 0; + loop { + let next = match bits >> (i % 8) & 1 { + 0 => iter.next(), + _ => iter.next_back(), + }; + let Some(next) = next else { break }; + out.push(next); + i += 1; + } + out +} + +/// What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then +/// drops or moves to the front. +fn retained(v: &mut Vec, mut keep: impl FnMut(&mut T) -> bool) { + let len = v.len(); + let p = v.as_mut_ptr(); + let mut write = 0; + for read in 0..len { + let cur = unsafe { &mut *p.add(read) }; + if !keep(cur) { + unsafe { ptr::drop_in_place(cur) }; + } else { + unsafe { ptr::copy(cur, p.add(write), 1) }; + write += 1; + } + } + // What `set_len` leaves past the length, which nothing drops again. + mem::forget(v.split_off(write)); +} + +/// The length of the `Vec` a pointer from a reference the body is given points at. +fn vec_len(v: &Vec) -> usize { + let p = &raw const *v; + unsafe { (*p).len() } +} + +/// `with` written whole through a pointer from a `&mut` the body is given, made each way +/// std makes one, which its caller's `Vec` holds after. +fn refilled(v: &mut Vec, with: Vec, how: u8) { + let p = match how % 4 { + 0 => v as *mut Vec, + 1 => ptr::from_mut(v), + 2 => NonNull::from(v).as_ptr(), + _ => Into::>>::into(v).as_ptr(), + }; + unsafe { *p = with }; + unsafe { (*p).push(how) }; +} + +/// The value a pointer from a shared reference the body is given points at, made each way +/// std makes one. +fn behind(r: &u32, how: u8) -> u32 { + let p = match how % 3 { + 0 => r as *const u32, + 1 => ptr::from_ref(r), + _ => NonNull::from(r).as_ptr(), + }; + unsafe { ptr::read(p) } +} + +/// Raw pointers from references: smallvec's `Drain` and `retain`, over values JS copies, a +/// library type, and an `Option`; pointers from a reference the body borrowed, which move +/// within the array it borrowed from; and from references the body is given, to a number +/// and to a `Vec`, read and written whole. +pub fn behind_refs(data: &[u8], out: &mut Vec) { + let bits = data.first().copied().unwrap_or(0); + let nums: Vec = data.iter().map(|&b| u32::from(b) * 3).collect(); + out.extend(drained(nums.clone(), bits).iter().map(|&n| n as u8)); + let words: Vec = data.iter().map(|b| b.to_string()).collect(); + for w in drained(words.clone(), bits) { + out.extend(w.bytes()); + } + let runs: Vec> = data.chunks(3).map(<[u8]>::to_vec).collect(); + for r in drained(runs.clone(), bits) { + out.push(r.len() as u8); + out.extend(r); + } + let opts: Vec> = data.iter().map(|&b| (b % 3 != 0).then_some(u16::from(b))).collect(); + for o in drained(opts, bits) { + out.extend(o.map_or([0xff, 0xff], u16::to_be_bytes)); + } + + let mut nums = nums; + retained(&mut nums, |n| { + *n += 1; + *n % 2 == 0 + }); + out.extend(nums.iter().map(|&n| n as u8)); + let mut words = words; + retained(&mut words, |w| { + w.push('!'); + w.len() % 2 == 0 + }); + out.extend(words.iter().flat_map(|w| w.bytes())); + let mut runs = runs; + retained(&mut runs, |r| { + r.push(7); + r[0] % 2 == 0 + }); + out.extend(runs.iter().flatten()); + + // From a reference the body borrowed, where the element is. + let mut nums: Vec = data.iter().map(|&b| u32::from(b)).collect(); + if let [_, _, ..] = nums[..] { + let first = &nums[0] as *const u32; + let last = &nums[nums.len() - 1] as *const u32; + out.push(unsafe { last.offset_from(first) } as u8); + for i in 0..nums.len() { + out.push(unsafe { *first.add(i) } as u8); + } + let second = ptr::from_ref(&nums[1]); + out.push(unsafe { *second.sub(1) } as u8); + let m = &mut nums[1] as *mut u32; + unsafe { *m.sub(1) = 9 }; + out.push(nums[0] as u8); + } + + let mut v = data.to_vec(); + out.push(vec_len(&v) as u8); + refilled(&mut v, data.iter().rev().copied().collect(), bits); + out.extend(&v); + for (i, &b) in data.iter().enumerate() { + out.push(behind(&u32::from(b), i as u8) as u8); + } +} diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index d7acc7b88..3d5a1563e 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -10,8 +10,8 @@ fixtures/reject/src/alloc.rs:bytes_as_values+3:3: a byte of memory from `alloc`, fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` to one to `pointers::Halves`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another +fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another -fixtures/reject/src/alloc.rs:vec_pointer+1:10: a raw pointer to `std::vec::Vec` behind a reference, which is the object and not where it is kept fixtures/reject/src/alloc.rs:zeroed+1:12: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:13: unsupported call to `std::alloc::alloc_zeroed` fixtures/reject/src/buffers.rs:blank_bytes+1:2: `Buf::copy_to_bytes` of `buffers::Blank`, a value JS copies, which is not supported diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index fb9f70ed7..4e629dde7 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -14,8 +14,8 @@ fixtures/reject/src/alloc.rs:bytes_as_values+3:3: a byte of memory from `alloc`, fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` to one to `pointers::Halves`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another +fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another -fixtures/reject/src/alloc.rs:vec_pointer+1:10: a raw pointer to `std::vec::Vec` behind a reference, which is the object and not where it is kept fixtures/reject/src/alloc.rs:zeroed+1:12: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:13: unsupported call to `std::alloc::alloc_zeroed` fixtures/reject/src/buffers.rs:blank_bytes+1:2: `Buf::copy_to_bytes` of `buffers::Blank`, a value JS copies, which is not supported diff --git a/ts/fixtures/reject/src/alloc.rs b/ts/fixtures/reject/src/alloc.rs index dd4e03d8b..d09a28cc9 100644 --- a/ts/fixtures/reject/src/alloc.rs +++ b/ts/fixtures/reject/src/alloc.rs @@ -24,10 +24,10 @@ pub fn allocated() -> u8 { } } -/// A pointer to a `Vec`, whose JS array is its heap, not where Rust keeps the `Vec`. -pub fn vec_pointer(v: &mut Vec) -> usize { - let p = &raw const *v; - unsafe { (*p).len() } +/// A pointer from a reference the body is given, which JS keeps in no array, moved past +/// it. +pub fn past_reference(r: &u32) -> u32 { + unsafe { *(r as *const u32).add(2) } } /// Casts between pointers to bytes and to values outside memory from `alloc`, which read diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index b5754cba1..5116be58e 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -4,8 +4,8 @@ roots = [ "alloc::alloc_retyped", "alloc::allocated", "alloc::bytes_as_values", + "alloc::past_reference", "alloc::reinterpreted", - "alloc::vec_pointer", "alloc::zeroed", "bound_ranges", "buffers::blank_bytes", diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index b32ff4e28..b52faf9df 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -533,6 +533,17 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { { done(p, false) } + // A pointer from a reference to a value JS keeps in a place, as `&raw const *r` makes. + ("ptr", "from_ref" | "from_mut") | ("From", "from") | ("Into", "into") + if let Some(r) = args[0].node.place() + && crate::lower::pointer::raw_pointee(tcx, dest.ty(&self.mir.local_decls, tcx).ty) + .is_some_and(|pointee| self.behind_place(r, pointee)) => + { + if let Some(why) = self.made_out_of_step(def_id, gargs, &args[0].node) { + return done(self.err(span, why), false); + } + done(self.behind_reference(r, span, out), false) + } // A place of no bytes has nothing to read or write, and every value of its type // is the one it holds. What made the `&mut` still runs, as its unread temporary. ("mem", "take" | "replace" | "swap") if self.b.t.zero_sized(targ(0)?) => match name.as_str() { diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 33fd92e18..3a91b89df 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -194,36 +194,27 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { op, _, ) if self.same_pointee(op, local) => self.origin(op), - Rvalue::RawPtr(mir::RawPtrKind::Const | mir::RawPtrKind::Mut, place) => { - match place.projection.as_slice() { - // `&raw mut *p` is `p`. - [ProjectionElem::Deref] => self.origin(&Operand::Copy(mir::Place::from(place.local))), - [.., ProjectionElem::Deref] => None, - [ - array @ .., - ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. }, - ] => (!place.projection.contains(&ProjectionElem::Deref)).then(|| Origin { - array: Array::Owned(mir::Place { - local: place.local, - projection: self.tcx.mk_place_elems(array), - }), - index: None, - }), - _ => Some(Origin { - array: Array::Lone(Location { - block: bb, - statement_index: i, - }), - index: Some(0), - }), - } - } + Rvalue::RawPtr(mir::RawPtrKind::Const | mir::RawPtrKind::Mut, place) => self.raw_origin( + *place, + Location { + block: bb, + statement_index: i, + }, + ), _ => None, }; } let (def_id, _, args) = self.def_call(local)?; let (owner, name) = library::key(self.tcx, def_id)?; match (owner.as_str(), name.as_str()) { + ("ptr", "from_ref" | "from_mut") | ("From", "from") | ("Into", "into") => { + let block = self.def_block(local)?; + let at = Location { + block, + statement_index: self.mir.basic_blocks[block].statements.len(), + }; + self.reference_origin(args[0].node.place()?, at) + } ("ptr" | "NonNull", "add" | "sub" | "offset") => { Some(self.moved(self.origin(&args[0].node)?, name.as_str(), &args[1].node)) } @@ -233,6 +224,77 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } } + /// Where a raw pointer to `place`, made at `at`, points, if the body shows it. + fn raw_origin(&self, place: mir::Place<'tcx>, at: Location) -> Option> { + match place.projection.as_slice() { + // `&raw mut *p` is `p`. + [ProjectionElem::Deref] if self.mir.local_decls[place.local].ty.is_raw_ptr() => { + self.origin(&Operand::Copy(mir::Place::from(place.local))) + } + [rest @ .., ProjectionElem::Deref] => self.reference_origin( + mir::Place { + local: place.local, + projection: self.tcx.mk_place_elems(rest), + }, + at, + ), + [ + array @ .., + ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. }, + ] => (!place.projection.contains(&ProjectionElem::Deref)).then(|| Origin { + array: Array::Owned(mir::Place { + local: place.local, + projection: self.tcx.mk_place_elems(array), + }), + index: None, + }), + _ => Some(Origin { + array: Array::Lone(at), + index: Some(0), + }), + } + } + + /// Where a raw pointer to what the reference `r` points at, made at `at`, points, as + /// [`Self::behind_reference`] makes it: where the body shows `r` borrowed, and else at + /// a place of its own, or, where `r` is an `rs.Place`, where that points. + fn reference_origin(&self, r: mir::Place<'tcx>, at: Location) -> Option> { + let ty = r.ty(&self.mir.local_decls, self.tcx).ty; + if !ty.is_ref() { + return None; + } + let pointee = self.tcx.mk_place_deref(r); + let borrowed = self.through_aliases(pointee); + if borrowed != pointee { + return self.raw_origin(borrowed, at); + } + if self.indexed(r).is_some() { + let (def_id, _, args) = self.def_call(r.as_local()?)?; + let (owner, name) = library::key(self.tcx, def_id)?; + if !matches!( + (owner.as_str(), name.as_str()), + ("Index" | "IndexMut", "index" | "index_mut") + ) { + return None; + } + let o = self.start(&args[0].node)?; + return Some(Origin { + index: o.index.zip(self.const_int(&args[1].node)).map(|(i, n)| i + n), + ..o + }); + } + if let Some(p) = self.reborrowed_pointer(r) { + return self.origin(&Operand::Copy(p)); + } + if self.b.t.place_pointee(ty).is_some() { + return None; + } + Some(Origin { + array: Array::Lone(at), + index: Some(0), + }) + } + /// `o` moved by the offset `op` as `add`, `sub`, or `offset` moves it. fn moved(&self, o: Origin<'tcx>, name: &str, op: &Operand<'tcx>) -> Origin<'tcx> { let n = self.const_int(op).map(|n| match name { diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index f93619b65..1f3e882b7 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -819,15 +819,8 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { matches!(self.mir.local_decls[place.local].ty.kind(), ty::RawPtr(..)) && !self.box_pointer(place.local); let e = match raw { true => Expr::rs("ptrWrite", vec![target, value]), - false => match self.b.t.write_in(pointee) { - Some(ObjectKind::Bytes) => target.member("set").call(vec![value]), - // A boxed slice is a JS array, whose length a write changes, unlike a - // tuple's or an array's. - Some(ObjectKind::Array) if self.b.t.erased(pointee).is_slice() => { - Expr::rs("arrayAssign", vec![target, value]) - } - Some(_) if self.b.t.moves_private(pointee) => Expr::rs("ptrWrite", vec![target, value]), - Some(_) => Expr::ident("Object").member("assign").call(vec![target, value]), + false => match self.write_into(target, value, pointee) { + Some(e) => e, None => self.err(span, format!("an assignment through a reference to `{pointee}`")), }, }; @@ -840,6 +833,19 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { out } + /// The write of `value`, a whole `pointee`, into `target`, the object a reference to it + /// is, in place, if JS can write one so. + pub(super) fn write_into(&self, target: Expr, value: Expr, pointee: Ty<'tcx>) -> Option { + Some(match self.b.t.write_in(pointee)? { + ObjectKind::Bytes => target.member("set").call(vec![value]), + // A boxed slice is a JS array, whose length a write changes, unlike a tuple's or + // an array's. + ObjectKind::Array if self.b.t.erased(pointee).is_slice() => Expr::rs("arrayAssign", vec![target, value]), + _ if self.b.t.moves_private(pointee) => Expr::rs("ptrWrite", vec![target, value]), + _ => Expr::ident("Object").member("assign").call(vec![target, value]), + }) + } + /// `target = value`, where `target` may be a struct's byte slice field held as its /// parts: each of its fields gets its part, in order, read before any is written where /// a part reads a field written before it. diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 6d127f34b..593621f28 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -70,114 +70,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // A bounds check of a `&mut` slice reads its length through a raw pointer that // only `PtrMetadata` reads, which reads the slice's. Rvalue::RawPtr(mir::RawPtrKind::FakeForPtrMetadata, place) => self.place_expr(*place, span, out), - Rvalue::RawPtr(_, place) => { - if self.union_whole(*place, span).is_some() { - return Some(self.err(span, "a pointer to a union's field kept in more than one slot")); - } - if let Some(why) = self.out_of_step(*place, false) { - return Some(self.err(span, why)); - } - let pointee = place.ty(&self.mir.local_decls, tcx).ty; - match self.b.t.pointee(pointee) { - Pointee::Object => self.place_expr(*place, span, out), - // `&raw mut *p` of a raw pointer is `p`; of a reference, the slice's start and - // length. - Pointee::Slice => match place.projection.as_slice() { - [rest @ .., mir::ProjectionElem::Deref] - if Place::ty_from(place.local, rest, &self.mir.local_decls, tcx) - .ty - .is_raw_ptr() => - { - let base = Place { - local: place.local, - projection: tcx.mk_place_elems(rest), - }; - self.place_expr(base, span, out) - } - _ => { - let value = self.place_expr(*place, span, out); - match crate::lower::pointer::slice_of_bytes(pointee) { - true => Expr::rs("rawByteSliceOf", vec![value]), - false => Expr::rs("rawSliceOf", vec![value]), - } - } - }, - // A slice of all of an array, as a borrow of it is. - Pointee::Array => { - if let Some(e) = self.array_subslice(mir::BorrowKind::Shared, *place, span, out) { - return Some(e); - } - let value = self.place_expr(*place, span, out); - match self.in_array(*place) { - true => value, - false => self.b.t.array_rs(pointee, "From").call(vec![value]), - } - } - // `&raw mut *p` is `p`. A pointer to `()` is an identity, which only another - // pointer is: not the `rs.Place` of a place or of a `&mut`, each borrow's own. - Pointee::Place => match place.projection.as_slice() { - [rest @ .., mir::ProjectionElem::Deref] - if !self.box_pointer(place.local) && { - let base = Place::ty_from(place.local, rest, &self.mir.local_decls, tcx).ty; - match self.b.t.identity_pointee(pointee) { - true => base.is_raw_ptr(), - false => self.b.t.place_pointee(base).is_some(), - } - } => - { - let base = Place { - local: place.local, - projection: tcx.mk_place_elems(rest), - }; - self.place_expr(base, span, out) - } - // A reference to an `Arc`'s value, which is that value's one place. - [mir::ProjectionElem::Deref] - if let Some(p) = self.arc_raw(&Operand::Copy(Place::from(place.local)), span, out) => - { - p - } - _ if self.b.t.identity_pointee(pointee) => self.err( - span, - "a raw pointer to a place of `()`, which has no address of its own", - ), - // A reference to an object is the object, not the place holding it. - [.., mir::ProjectionElem::Deref] if !self.box_pointer(place.local) => self.err( - span, - format!( - "a raw pointer to `{pointee}` behind a reference, which is the object and not where it is kept" - ), - ), - // A pointer to an element is where it is in its array, which pointer - // arithmetic moves within. - [ - array @ .., - mir::ProjectionElem::Index(_) | mir::ProjectionElem::ConstantIndex { .. }, - ] => { - // It points into the array, which must stay where it is. - let array = Place { - local: place.local, - projection: tcx.mk_place_elems(array), - }; - if let Some(why) = self.out_of_step(array, false) { - return Some(self.err(span, why)); - } - let target = self.place_expr(*place, span, out); - let target = self.stable_target(*place, target, span, out); - match target.unlocated() { - Expr::Index(array, index) => { - Expr::rs("arrayAsPtr", vec![*array.clone(), *index.clone()]) - } - _ => self.place_of(target, None), - } - } - _ => { - let target = self.place_expr(*place, span, out); - self.make_place(*place, target, span, out) - } - }, - } - } + Rvalue::RawPtr(_, place) => self.raw_pointer(*place, span, out), Rvalue::Reborrow(..) => self.err(span, "reborrowing a type by its `Reborrow` impl is not supported"), Rvalue::ThreadLocalRef(_) => self.err(span, "thread locals are not supported"), Rvalue::WrapUnsafeBinder(..) => self.err(span, "unsafe binders are not supported"), @@ -262,6 +155,229 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { }) } + /// A raw pointer to `place`. + pub(super) fn raw_pointer(&mut self, place: Place<'tcx>, span: Span, out: &mut Vec) -> Expr { + let tcx = self.tcx; + if self.union_whole(place, span).is_some() { + return self.err(span, "a pointer to a union's field kept in more than one slot"); + } + if let Some(why) = self.out_of_step(place, false) { + return self.err(span, why); + } + let pointee = place.ty(&self.mir.local_decls, tcx).ty; + match self.b.t.pointee(pointee) { + Pointee::Object => self.place_expr(place, span, out), + // `&raw mut *p` of a raw pointer is `p`; of a reference, the slice's start and + // length. + Pointee::Slice => match place.projection.as_slice() { + [rest @ .., mir::ProjectionElem::Deref] + if Place::ty_from(place.local, rest, &self.mir.local_decls, tcx) + .ty + .is_raw_ptr() => + { + let base = Place { + local: place.local, + projection: tcx.mk_place_elems(rest), + }; + self.place_expr(base, span, out) + } + _ => { + let value = self.place_expr(place, span, out); + match crate::lower::pointer::slice_of_bytes(pointee) { + true => Expr::rs("rawByteSliceOf", vec![value]), + false => Expr::rs("rawSliceOf", vec![value]), + } + } + }, + // A slice of all of an array, as a borrow of it is. + Pointee::Array => { + if let Some(e) = self.array_subslice(mir::BorrowKind::Shared, place, span, out) { + return e; + } + let value = self.place_expr(place, span, out); + match self.in_array(place) { + true => value, + false => self.b.t.array_rs(pointee, "From").call(vec![value]), + } + } + // `&raw mut *p` is `p`. A pointer to `()` is an identity, which only another + // pointer is: not the `rs.Place` of a place or of a `&mut`, each borrow's own. + Pointee::Place => match place.projection.as_slice() { + [rest @ .., mir::ProjectionElem::Deref] + if !self.box_pointer(place.local) && { + let base = Place::ty_from(place.local, rest, &self.mir.local_decls, tcx).ty; + match self.b.t.identity_pointee(pointee) { + true => base.is_raw_ptr(), + false => self.b.t.place_pointee(base).is_some(), + } + } => + { + let base = Place { + local: place.local, + projection: tcx.mk_place_elems(rest), + }; + let borrowed = match Place::ty_from(place.local, rest, &self.mir.local_decls, tcx).ty.is_ref() { + true => self.borrowed_pointer(base, span, out), + false => None, + }; + match borrowed { + Some(p) => p, + None => self.place_expr(base, span, out), + } + } + // A reference to an `Arc`'s value, which is that value's one place. + [mir::ProjectionElem::Deref] + if let Some(p) = self.arc_raw(&Operand::Copy(Place::from(place.local)), span, out) => + { + p + } + _ if self.b.t.identity_pointee(pointee) => self.err( + span, + "a raw pointer to a place of `()`, which has no address of its own", + ), + // A reference to an object is the object, not the place holding it. + // A reference to an object is the object, not the place holding it. + [rest @ .., mir::ProjectionElem::Deref] if !self.box_pointer(place.local) => { + let reference = Place { + local: place.local, + projection: tcx.mk_place_elems(rest), + }; + self.behind_reference(reference, span, out) + } + // A pointer to an element is where it is in its array, which pointer + // arithmetic moves within. + [ + array @ .., + mir::ProjectionElem::Index(_) | mir::ProjectionElem::ConstantIndex { .. }, + ] => { + // It points into the array, which must stay where it is. + let array = Place { + local: place.local, + projection: tcx.mk_place_elems(array), + }; + if let Some(why) = self.out_of_step(array, false) { + return self.err(span, why); + } + let target = self.place_expr(place, span, out); + let target = self.stable_target(place, target, span, out); + match target.unlocated() { + Expr::Index(array, index) => { + Expr::rs("arrayAsPtr", vec![*array.clone(), *index.clone()]) + } + _ => self.place_of(target, None), + } + } + _ => { + let target = self.place_expr(place, span, out); + self.make_place(place, target, span, out) + } + }, + } + } + + /// A raw pointer to what the reference `r` points at, a value whose pointer is an + /// `rs.Place`, where `r` is no `rs.Place` itself but the value: the place the body shows + /// `r` borrowed, as [`Self::borrowed_pointer`] finds it. Otherwise it is a place of its + /// own, as `ptr::from_ref` makes: of the value, where `r` is shared and nothing may write + /// the value while the pointer lives, and else writing a whole value into the object `r` + /// is in place, as `*r = v` does. + pub(super) fn behind_reference(&mut self, r: Place<'tcx>, span: Span, out: &mut Vec) -> Expr { + let tcx = self.tcx; + if let Some(p) = self.borrowed_pointer(r, span, out) { + return p; + } + let pointee = tcx.mk_place_deref(r); + let ty = pointee.ty(&self.mir.local_decls, tcx).ty; + let shared = matches!( + r.ty(&self.mir.local_decls, tcx).ty.kind(), + ty::Ref(_, _, rustc_ast::Mutability::Not) + ); + let value = self.place_expr(pointee, span, out); + if shared && ty.is_freeze(tcx, self.b.t.typing_env_pub()) { + return Expr::Object(vec![(Key::Name("v".into()), value)]); + } + let target = self.b.spill(value, "target", out); + let written = self.b.fresh("value"); + let Some(write) = self.write_into(target.clone(), Expr::ident(&written), ty) else { + return self.err( + span, + format!("a raw pointer to `{ty}` behind a reference, which JS can't write a whole value into in place"), + ); + }; + let get = Expr::Lambda(Vec::new(), Box::new(target)); + let set = Expr::Lambda( + vec![Param { + name: written, + ty: None, + }], + Box::new(write), + ); + Expr::rs("place", vec![get, set]) + } + + /// The raw pointer to the place the body shows the reference `r` borrowed, if it does: + /// the place an alias of a borrow stands for, the element an index lends, or where the + /// raw pointer `r` reborrows what it points at from points. + fn borrowed_pointer(&mut self, r: Place<'tcx>, span: Span, out: &mut Vec) -> Option { + let pointee = self.tcx.mk_place_deref(r); + let borrowed = self.through_aliases(pointee); + if borrowed != pointee { + return Some(self.raw_pointer(borrowed, span, out)); + } + if let Some((array, index)) = self.indexed(r) { + return Some(Expr::rs("arrayAsPtr", vec![array, index])); + } + let p = self.reborrowed_pointer(r)?; + Some(self.place_expr(p, span, out)) + } + + /// Whether a raw pointer to `pointee`, made from the reference `r`, is what + /// [`Self::behind_reference`] makes: one to a value whose pointer is an `rs.Place`, which + /// `r` is not. + pub(super) fn behind_place(&self, r: Place<'tcx>, pointee: Ty<'tcx>) -> bool { + let ty = r.ty(&self.mir.local_decls, self.tcx).ty; + let t = &self.b.t; + ty.is_ref() + && t.place_pointee(ty).is_none() + && t.pointee(pointee) == Pointee::Place + && !t.identity_pointee(pointee) + && !t.zero_sized(pointee) + } + + /// The array and index of the element the reference `r` is an alias of, as indexing a + /// slice, an array, or a `Vec` by a number makes one. + pub(super) fn indexed(&self, r: Place<'tcx>) -> Option<(Expr, Expr)> { + let local = r.as_local()?; + if self.def_rvalue(local).is_some() { + return None; + } + let Slot::Alias(Some(target)) = &self.slots[local] else { + return None; + }; + let Expr::Index(array, index) = target.unlocated() else { + return None; + }; + Some((*array.clone(), *index.clone())) + } + + /// The raw pointer whose pointee the reference `r` borrows, if the body shows it: `r` is + /// `&*p` or `&mut *p`, each of which it assigns once, so `p` still points there. + pub(super) fn reborrowed_pointer(&self, r: Place<'tcx>) -> Option> { + let Rvalue::Ref(_, _, p) = self.def_rvalue(self.traced(&Operand::Copy(r))?)? else { + return None; + }; + let [mir::ProjectionElem::Deref] = p.projection.as_slice() else { + return None; + }; + let pointer = Place::from(p.local); + let fixed = match self.is_arg(p.local) { + true => self.defs[p.local] == 0, + false => self.traced(&Operand::Copy(pointer)).is_some(), + }; + let raw = self.mir.local_decls[p.local].ty.is_raw_ptr() && !self.box_pointer(p.local); + (raw && fixed && !self.borrowed[p.local]).then_some(pointer) + } + /// An unsigned integer of at most 32 bits, as a `u32`. fn half(&mut self, op: &Operand<'tcx>, span: Span, out: &mut Vec) -> Expr { let from = op.ty(&self.mir.local_decls, self.tcx); diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 087e48216..9e16fe4e0 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -12,6 +12,11 @@ //! pointer arithmetic moves within the array. A pointer to an element JS keeps as an //! object is that object, which knows no array it is in, so arithmetic on one is refused. //! +//! A reference to a value JS copies is the value, not where it is kept, so a pointer made +//! from one is the place the body shows the reference borrowed, and else a place of its +//! own: of a shared reference's value, which nothing may write while the pointer lives, +//! or writing a whole value into the object a `&mut` is in place. +//! //! A pointer to `()`, as std erases one, is an identity that compares as itself: null, //! `rs.DANGLING_UNIT`, or a waker's data. One to a place of `()` or from a reference to //! one is refused, as an `rs.Place` of it would be an identity of its own each borrow. @@ -1105,7 +1110,13 @@ pub fn raw_pointee<'tcx>(tcx: ty::TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option /// The value `p`, a pointer to `ty`, points at, as a value of its own. fn read<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, ty: Ty<'tcx>, span: Span) -> Expr { let value = match b.t.pointee(ty) { - Pointee::Place => p.member("v"), + // The value a pointer made here of a shared reference holds. + Pointee::Place => match p { + Expr::Object(mut members) if matches!(members.as_slice(), [(Key::Name(v), _)] if v == "v") => { + members.pop().expect("one member").1 + } + p => p.member("v"), + }, Pointee::Object => p, Pointee::Array => { let ty = b.t.uninit_inner(ty); From 19345e17d81b94f3601421be1dbbe0799a2ffc78 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 18:54:06 -0800 Subject: [PATCH 03/29] style(ts): format Co-Authored-By: Claude Opus 5.5 --- ts/src/lower/mir/rvalue.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 593621f28..7884af69a 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -216,7 +216,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { local: place.local, projection: tcx.mk_place_elems(rest), }; - let borrowed = match Place::ty_from(place.local, rest, &self.mir.local_decls, tcx).ty.is_ref() { + let borrowed = match Place::ty_from(place.local, rest, &self.mir.local_decls, tcx) + .ty + .is_ref() + { true => self.borrowed_pointer(base, span, out), false => None, }; @@ -261,9 +264,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let target = self.place_expr(place, span, out); let target = self.stable_target(place, target, span, out); match target.unlocated() { - Expr::Index(array, index) => { - Expr::rs("arrayAsPtr", vec![*array.clone(), *index.clone()]) - } + Expr::Index(array, index) => Expr::rs("arrayAsPtr", vec![*array.clone(), *index.clone()]), _ => self.place_of(target, None), } } From 71cd73303af521df4f6f548d0969176fa916b58c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 19:11:19 -0800 Subject: [PATCH 04/29] test(ts): regenerate the census fixture after merging main Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/census/census.md | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/ts/fixtures/census/census.md b/ts/fixtures/census/census.md index 3f0f658c1..aecd16873 100644 --- a/ts/fixtures/census/census.md +++ b/ts/fixtures/census/census.md @@ -1,6 +1,6 @@ # Census of `census-fixture` -- Refusals: 13 +- Refusals: 9 - Function instances: 6 - Generic functions no instance reached, lowered over their type parameters: 1 @@ -8,7 +8,6 @@ | Count | Construct | First | | ---: | --- | --- | -| 4 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | fixtures/census/src/lib.rs:9:2 | | 3 | 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 | @@ -21,41 +20,28 @@ The type or module each refused type or path belongs to. | Count | API | First | | ---: | --- | --- | -| 4 | `u32` | fixtures/census/src/lib.rs:9:2 | | 3 | `usize` | fixtures/census/src/lib.rs:9:2 | -| 2 | `u64` | fixtures/census/src/lib.rs:19:47 | +| 2 | `u32` | fixtures/census/src/lib.rs:19:12 | | 2 | `u8` | fixtures/census/src/lib.rs:19:12 | | 1 | `*const T` | fixtures/census/src/lib.rs:14:2 | | 1 | `*const bytes::Bytes` | fixtures/census/src/lib.rs:45:2 | | 1 | `*const u32` | fixtures/census/src/lib.rs:9:2 | | 1 | `Bits` | fixtures/census/src/lib.rs:28:1 | -| 1 | `bytes::Bytes` | fixtures/census/src/lib.rs:45:2 | +| 1 | `u64` | fixtures/census/src/lib.rs:19:47 | ## By type or path | Count | Construct | Type or path | First | | ---: | --- | --- | --- | -| 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 | 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 bytes::Bytes`, `usize` | fixtures/census/src/lib.rs:45: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 | `bytes::Bytes` | fixtures/census/src/lib.rs:45:2 | -| 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 | union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart | `Bits`, `u32` | fixtures/census/src/lib.rs:28:1 | ## Spans -
a raw pointer to `_` behind a reference, which is the object and not where it is kept (4) - -- fixtures/census/src/lib.rs:9:2: a raw pointer to `u32` behind a reference, which is the object and not where it is kept -- fixtures/census/src/lib.rs:19:13: a raw pointer to `u32` behind a reference, which is the object and not where it is kept -- fixtures/census/src/lib.rs:19:48: a raw pointer to `u64` behind a reference, which is the object and not where it is kept -- fixtures/census/src/lib.rs:45:2: a raw pointer to `bytes::Bytes` behind a reference, which is the object and not where it is kept - -
-
a cast from `_` to `_`, between a pointer and an integer (3) - fixtures/census/src/lib.rs:9:2: a cast from `*const u32` to `usize`, between a pointer and an integer From 5a40085951572073e19950592505fa04d70dbece Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 19:18:31 -0800 Subject: [PATCH 05/29] docs(quest): the census at the pin after a raw pointer behind a reference Co-Authored-By: Claude Opus 5.5 --- quest/m2/ts-model/README.md | 4 ++-- quest/m3/ts-unsafe/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index c4458a33f..c84b89c75 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -45,13 +45,13 @@ 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: 71 refusals. `Timestamp` alone refuses nothing, and +The census at the pin on 2026-11-07: 45 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 -refuses for good, and smallvec's union over a type parameter and raw parts (10) wait on +refuses for good, and smallvec's union over a type parameter and raw parts (4) 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`. diff --git a/quest/m3/ts-unsafe/README.md b/quest/m3/ts-unsafe/README.md index f61d838b8..43c532c5f 100644 --- a/quest/m3/ts-unsafe/README.md +++ b/quest/m3/ts-unsafe/README.md @@ -63,7 +63,7 @@ construct still open at `b3df54b6c`, all in m3: [#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. +Census at the moq pin on 2026-11-07: model 45, kio 72, moq-net 347 refusals. Public API: the translator's output. From 30e8b1225333034edc725e04cdb09d577cb65ce1 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 19:38:52 -0800 Subject: [PATCH 06/29] fix(ts): keep an Arc's value's pointer its one place, and read an enum of cells through a shared reference's pointer Co-Authored-By: Claude Opus 5.5 --- .../lang/pointers/expected/core/bool.ts | 7 ++++ .../lang/pointers/expected/pointers.ts | 42 ++++++++++++++++--- ts/fixtures/lang/pointers/src/pointers.rs | 29 +++++++++++-- ts/src/lower/mir/origin.rs | 3 +- ts/src/lower/mir/rvalue.rs | 7 +++- 5 files changed, 77 insertions(+), 11 deletions(-) diff --git a/ts/fixtures/lang/pointers/expected/core/bool.ts b/ts/fixtures/lang/pointers/expected/core/bool.ts index df98e8987..ba69bc95a 100644 --- a/ts/fixtures/lang/pointers/expected/core/bool.ts +++ b/ts/fixtures/lang/pointers/expected/core/bool.ts @@ -10,6 +10,13 @@ export function thenSome$1gvhopg(self: boolean, t: rs.u16): rs.u16 | undefined { return t; } +export function then$01qpu7s(self: boolean, f: () => rs.Cell): rs.Cell | undefined { + if (!self) { + return undefined; + } + return f(); +} + export function then$0bgt67i(self: boolean, f: () => rs.Weak): rs.Weak | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 990b05a91..8469d0833 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -2,7 +2,7 @@ import * as rs from "./rs.ts"; import { copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_Twin, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; -import { thenSome$1gvhopg, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; +import { thenSome$1gvhopg, then$01qpu7s, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; import type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; @@ -3174,11 +3174,31 @@ export function behind(r: rs.u32, how: rs.u8): rs.u32 { return p.v; } +/** + * Whether the `Option` a pointer from a shared reference the body is given points at + * holds a cell, and the number in it, read through the pointer made each way std makes + * one. + */ +export function cellBehind(r: rs.Cell | undefined, how: rs.u8): rs.u32 { + let p!: rs.Place | undefined>; + if (rs.u8Rem(how, 2) === 0) { + p = { v: r }; + } else { + p = { v: r }; + } + const _6: rs.Cell | undefined = p.v; + if (_6 === undefined) { + return 0; + } + return rs.u32Add(_6.v, 1); +} + /** * Raw pointers from references: smallvec's `Drain` and `retain`, over values JS copies, a * library type, and an `Option`; pointers from a reference the body borrowed, which move - * within the array it borrowed from; and from references the body is given, to a number - * and to a `Vec`, read and written whole. + * within the array it borrowed from; from references the body is given, to a number, to an + * `Option` of a cell, and to a `Vec`, read and written whole; and from references to an + * `Arc`'s value, measured against each other. */ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { let _112!: rs.usize; @@ -3261,8 +3281,20 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { if (_153 === rs.DONE) { break; } - rs.byteVecPush(out, behind(_153[1] as rs.u32, _153[0] & 0xff) & 0xff); - } + const i2: rs.usize = _153[0]; + const b: rs.u8 = _153[1]; + rs.byteVecPush(out, behind(b as rs.u32, i2 & 0xff) & 0xff); + rs.byteVecPush(out, cellBehind(then$01qpu7s(rs.u8Rem(b, 3) !== 0, (): rs.Cell => { + return new rs.Cell(b as rs.u32); + }), b) & 0xff); + } + const arc: rs.Arc = new rs.Arc(bits); + const _178: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); + const _181: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); + const p: rs.Place = _178; + rs.byteVecPush(out, rs.ptrOffsetFrom(p, _181) & 0xff); + rs.byteVecPush(out, p.v); + arc[Symbol.dispose](); } //# sourceMappingURL=pointers.ts.map diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index c4428c980..5cd12523e 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2497,10 +2497,25 @@ fn behind(r: &u32, how: u8) -> u32 { unsafe { ptr::read(p) } } +/// Whether the `Option` a pointer from a shared reference the body is given points at +/// holds a cell, and the number in it, read through the pointer made each way std makes +/// one. +fn cell_behind(r: &Option>, how: u8) -> u32 { + let p = match how % 2 { + 0 => &raw const *r, + _ => ptr::from_ref(r), + }; + match unsafe { &*p } { + Some(cell) => cell.get() + 1, + None => 0, + } +} + /// Raw pointers from references: smallvec's `Drain` and `retain`, over values JS copies, a /// library type, and an `Option`; pointers from a reference the body borrowed, which move -/// within the array it borrowed from; and from references the body is given, to a number -/// and to a `Vec`, read and written whole. +/// within the array it borrowed from; from references the body is given, to a number, to an +/// `Option` of a cell, and to a `Vec`, read and written whole; and from references to an +/// `Arc`'s value, measured against each other. pub fn behind_refs(data: &[u8], out: &mut Vec) { let bits = data.first().copied().unwrap_or(0); let nums: Vec = data.iter().map(|&b| u32::from(b) * 3).collect(); @@ -2560,5 +2575,13 @@ pub fn behind_refs(data: &[u8], out: &mut Vec) { out.extend(&v); for (i, &b) in data.iter().enumerate() { out.push(behind(&u32::from(b), i as u8) as u8); - } + let cell = (b % 3 != 0).then(|| Cell::new(u32::from(b))); + out.push(cell_behind(&cell, b) as u8); + } + // Pointers from references to an `Arc`'s value, which is its one place. + let arc = Arc::new(u32::from(bits)); + let p = ptr::from_ref(&*arc); + let q = ptr::from_ref(&*arc); + out.push(unsafe { p.offset_from(q) } as u8); + out.push(unsafe { *q } as u8); } diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 3a91b89df..648b3f732 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -260,7 +260,8 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { /// a place of its own, or, where `r` is an `rs.Place`, where that points. fn reference_origin(&self, r: mir::Place<'tcx>, at: Location) -> Option> { let ty = r.ty(&self.mir.local_decls, self.tcx).ty; - if !ty.is_ref() { + // A reference to an `Arc`'s value points at that value's one place. + if !ty.is_ref() || self.arc_of(&Operand::Copy(r)).is_some() { return None; } let pointee = self.tcx.mk_place_deref(r); diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 7884af69a..1eec69c0c 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -280,7 +280,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { /// `rs.Place`, where `r` is no `rs.Place` itself but the value: the place the body shows /// `r` borrowed, as [`Self::borrowed_pointer`] finds it. Otherwise it is a place of its /// own, as `ptr::from_ref` makes: of the value, where `r` is shared and nothing may write - /// the value while the pointer lives, and else writing a whole value into the object `r` + /// a whole value through the pointer, and else writing a whole value into the object `r` /// is in place, as `*r = v` does. pub(super) fn behind_reference(&mut self, r: Place<'tcx>, span: Span, out: &mut Vec) -> Expr { let tcx = self.tcx; @@ -293,8 +293,11 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { r.ty(&self.mir.local_decls, tcx).ty.kind(), ty::Ref(_, _, rustc_ast::Mutability::Not) ); + // Through a shared reference, a whole write is undefined unless every byte of the + // value is in an `UnsafeCell`, as no enum's tag is. + let tagged = matches!(ty.kind(), ty::Adt(adt, _) if adt.is_enum() && adt.variants().len() > 1); let value = self.place_expr(pointee, span, out); - if shared && ty.is_freeze(tcx, self.b.t.typing_env_pub()) { + if shared && (tagged || ty.is_freeze(tcx, self.b.t.typing_env_pub())) { return Expr::Object(vec![(Key::Name("v".into()), value)]); } let target = self.b.spill(value, "target", out); From 437a61a706ed24a50473b40fe6a5ebfc1a416643 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 19:44:49 -0800 Subject: [PATCH 07/29] test(ts): snapshot the Arc pointers' case Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/pointers/expected/pointers.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 8469d0833..bf22d7baf 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -3288,12 +3288,10 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { return new rs.Cell(b as rs.u32); }), b) & 0xff); } - const arc: rs.Arc = new rs.Arc(bits); - const _178: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); - const _181: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); - const p: rs.Place = _178; - rs.byteVecPush(out, rs.ptrOffsetFrom(p, _181) & 0xff); - rs.byteVecPush(out, p.v); + const arc: rs.Arc = new rs.Arc(bits as rs.u32); + const q: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); + rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arcRaw(arc, rs.arcPlace(arc), "Arc"), q) & 0xff); + rs.byteVecPush(out, q.v & 0xff); arc[Symbol.dispose](); } From bce76b5c134fca6d0c5c06caf90135a11df316e9 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 19:49:33 -0800 Subject: [PATCH 08/29] fix(ts): tell a tag outside every UnsafeCell by the value's layout Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.txt | 1 + ts/fixtures/reject/expected.txt | 1 + ts/fixtures/reject/src/alloc.rs | 7 +++++++ ts/fixtures/reject/ts.toml | 1 + ts/src/lower/mir/rvalue.rs | 9 ++++++--- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index a8a994c16..7fe94f034 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -10,6 +10,7 @@ fixtures/reject/src/alloc.rs:bytes_as_values+3:3: a byte of memory from `alloc`, fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` to one to `pointers::Halves`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another +fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:12: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index b73a09827..b82c6fd82 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -14,6 +14,7 @@ fixtures/reject/src/alloc.rs:bytes_as_values+3:3: a byte of memory from `alloc`, fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` to one to `pointers::Halves`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another +fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:12: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another diff --git a/ts/fixtures/reject/src/alloc.rs b/ts/fixtures/reject/src/alloc.rs index d09a28cc9..127ba0ea8 100644 --- a/ts/fixtures/reject/src/alloc.rs +++ b/ts/fixtures/reject/src/alloc.rs @@ -24,6 +24,13 @@ pub fn allocated() -> u8 { } } +/// A whole value written through a pointer from a shared reference to a `Result` of a cell +/// whose only inhabited variant leaves it no tag, all of it in an `UnsafeCell`, which JS +/// can't write into in place. +pub fn infallible_cell(r: &Result, std::convert::Infallible>) { + unsafe { std::ptr::from_ref(r).cast_mut().write(Ok(std::cell::Cell::new(19))) } +} + /// A pointer from a reference the body is given, which JS keeps in no array, moved past /// it. pub fn past_reference(r: &u32) -> u32 { diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 769b86785..2a268d10a 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -4,6 +4,7 @@ roots = [ "alloc::alloc_retyped", "alloc::allocated", "alloc::bytes_as_values", + "alloc::infallible_cell", "alloc::past_reference", "alloc::reinterpreted", "alloc::zeroed", diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 1eec69c0c..1f4b30505 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -294,10 +294,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { ty::Ref(_, _, rustc_ast::Mutability::Not) ); // Through a shared reference, a whole write is undefined unless every byte of the - // value is in an `UnsafeCell`, as no enum's tag is. - let tagged = matches!(ty.kind(), ty::Adt(adt, _) if adt.is_enum() && adt.variants().len() > 1); + // value is in an `UnsafeCell`, as no tag or niche of a layout's variants is. + let env = self.b.t.typing_env_pub(); + let tagged = tcx + .layout_of(env.as_query_input(ty)) + .is_ok_and(|layout| matches!(layout.variants, rustc_abi::Variants::Multiple { .. })); let value = self.place_expr(pointee, span, out); - if shared && (tagged || ty.is_freeze(tcx, self.b.t.typing_env_pub())) { + if shared && (tagged || ty.is_freeze(tcx, env)) { return Expr::Object(vec![(Key::Name("v".into()), value)]); } let target = self.b.spill(value, "target", out); From 0766fe431d54c98999daf123bb527b5305d8999c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 21:39:43 -0800 Subject: [PATCH 09/29] chore(ts): regenerate the pointer snapshots for rs.String Co-Authored-By: Claude Opus 5.5 --- .../lang/pointers/expected/core/option.ts | 2 +- .../pointers/expected/pointers.Drained.ts | 10 ++-- .../lang/pointers/expected/pointers.ts | 50 ++++++++++--------- ts/fixtures/lang/pointers/src/pointers.rs | 4 +- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/ts/fixtures/lang/pointers/expected/core/option.ts b/ts/fixtures/lang/pointers/expected/core/option.ts index 9d27f5111..9244a4c57 100644 --- a/ts/fixtures/lang/pointers/expected/core/option.ts +++ b/ts/fixtures/lang/pointers/expected/core/option.ts @@ -55,7 +55,7 @@ export function map$00buaky(self: rs.u16 | undefined | rs.None, f: (a0: rs.u16 | return f(self); } -export function map$18tou5q(self: string | undefined, f: (a0: string) => string): string | undefined { +export function map$1lzqy5c(self: rs.String | undefined, f: (a0: rs.String) => rs.String): rs.String | undefined { if (self === undefined) { return undefined; } diff --git a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts index b49b63d8b..da114c70c 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { map$00buaky as option_map$00buaky, map$18tou5q as option_map$18tou5q, map$1h9tk16 as option_map$1h9tk16, map$070fw7m as option_map$070fw7m } from "./core/option.ts"; +import { map$00buaky as option_map$00buaky, map$1lzqy5c as option_map$1lzqy5c, map$1h9tk16 as option_map$1h9tk16, map$070fw7m as option_map$070fw7m } from "./core/option.ts"; /** * What smallvec's `Drain` is: the elements of a slice nothing will drop again, each read @@ -17,8 +17,8 @@ export function next$iter_Iterator_Option_u16(self: Drained) }), rs.None); } -export function next$iter_Iterator_String(self: Drained): string | rs.Done { - return rs.iterFromOption(option_map$18tou5q(rs.iterToOption((self.iter as rs.Iter).next()), (r: string): string => { +export function next$iter_Iterator_String(self: Drained): rs.String | rs.Done { + return rs.iterFromOption(option_map$1lzqy5c(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.String): rs.String => { return { v: r }.v; })); } @@ -41,8 +41,8 @@ export function nextBack$iter_DoubleEndedIterator_Option_u16(self: Drained): string | undefined { - return option_map$18tou5q(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: string): string => { +export function nextBack$iter_DoubleEndedIterator_String(self: Drained): rs.String | undefined { + return option_map$1lzqy5c(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.String): rs.String => { return { v: r }.v; }); } diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 7f2d45902..421328885 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -3002,10 +3002,10 @@ export function drained$Option_u16(v: rs.Vec, bits: rs.u8): } /** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ -export function drained$String(v: rs.Vec, bits: rs.u8): rs.Vec { - let next!: string | undefined; - const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; - const out: rs.Vec = new rs.Vec(); +export function drained$String(v: rs.Vec, bits: rs.u8): rs.Vec { + let next!: rs.String | undefined; + const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const out: rs.Vec = new rs.Vec(); let i: rs.i32 = 0; while (true) { if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { @@ -3068,16 +3068,16 @@ export function drained$u32(v: rs.Vec, bits: rs.u8): rs.Vec { * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then * drops or moves to the front. */ -export function retained$String_pointers_behind_refs_closure6(v: rs.Vec, keep: (a0: rs.Place) => boolean): void { +export function retained$String_pointers_behind_refs_closure6(v: rs.Vec, keep: (a0: rs.String) => boolean): void { let _10!: rs.usize; const len: rs.usize = v.items.length; - const p: rs.Place = rs.arrayAsPtr(v.items); + const p: rs.Place = rs.arrayAsPtr(v.items); let write: rs.usize = 0; let iterStart: rs.usize = 0; const iterEnd: rs.usize = len; while ((_10 = iterStart++) < iterEnd) { - const _15: rs.Place = rs.ptrAdd(p, _10); - if (keep(_15)) { + const _15: rs.Place = rs.ptrAdd(p, _10); + if (keep(_15.v)) { rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); write = rs.usizeAdd(write, 1); } @@ -3208,17 +3208,19 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(drained$u32(rs.vecFrom(nums.items.slice()), bits).items), (_23: rs.u32): rs.u8 => { return _23 & 0xff; }), undefined, true); - const words: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b3: rs.u8): string => { - return rs.fmtToString((f: rs.Formatter) => rs.u8Display(b3, f)); + const words: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b3: rs.u8): rs.String => { + return rs.fmtToString((f: rs.Formatter) => rs.u8Display(b3, f), 0); }), undefined, 12, true); - for (const _25 of drained$String(rs.vecFrom(words.items.slice()), bits).items) { - rs.iterExtendBytes(out, rs.strBytes(_25), undefined, true); + for (const _25 of drained$String(rs.vecFrom(words.items.map((a0: rs.String): rs.String => { + return rs.stringClone(a0); + })), bits).items) { + rs.iterExtendBytes(out, rs.strBytes(_25.v), undefined, true); } - const runs: rs.Vec = rs.iterToVec(rs.iterMap(rs.byteSliceChunks(data, 3), (a0: rs.ByteSlice): rs.ByteVec => { - return rs.byteSliceToByteVec(a0); + const runs: rs.Vec = rs.iterToVec(rs.iterMap(rs.byteSliceChunks(data, 3), (a02: rs.ByteSlice): rs.ByteVec => { + return rs.byteSliceToByteVec(a02); }), undefined, 12, true); - for (const r of drained$Vec_u8(rs.vecFrom(runs.items.map((a02: rs.ByteVec): rs.ByteVec => { - return rs.byteVecFrom(rs.byteVecView(a02)); + for (const r of drained$Vec_u8(rs.vecFrom(runs.items.map((a03: rs.ByteVec): rs.ByteVec => { + return rs.byteVecFrom(rs.byteVecView(a03)); })), bits).items) { rs.byteVecPush(out, r.length & 0xff); rs.iterExtendBytes(out, rs.byteVecIntoIter(r), undefined, true); @@ -3227,8 +3229,8 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { const b4: rs.u8 = _24; return thenSome$1gvhopg(rs.u8Rem(b4, 3) !== 0, b4 as rs.u16); }), undefined, 4, true), bits).items) { - rs.iterExtendBytes(out, rs.iterSlice(option_mapOr$0smr0aj(_56, Uint8Array.of(255, 255), (a03: rs.u16): Uint8Array => { - return rs.u16ToBeBytes(a03); + rs.iterExtendBytes(out, rs.iterSlice(option_mapOr$0smr0aj(_56, Uint8Array.of(255, 255), (a04: rs.u16): Uint8Array => { + return rs.u16ToBeBytes(a04); })), undefined, true); } const nums2: rs.Vec = nums; @@ -3239,13 +3241,13 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(nums2.items), (_26: rs.u32): rs.u8 => { return _26 & 0xff; }), undefined, true); - const words2: rs.Vec = words; - retained$String_pointers_behind_refs_closure6(words2, (w2: rs.Place): boolean => { - w2.v = w2.v + "!"; - return rs.usizeRem(rs.stringLen(w2.v), 2) === 0; + const words2: rs.Vec = words; + retained$String_pointers_behind_refs_closure6(words2, (w2: rs.String): boolean => { + rs.stringPush(w2, 33); + return rs.usizeRem(w2.len, 2) === 0; }); - rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(words2.items), (w3: string): rs.Iter => { - return rs.strBytes(w3); + rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(words2.items), (w3: rs.String): rs.Iter => { + return rs.strBytes(w3.v); }, undefined, true), "next"); const runs2: rs.Vec = runs; retained$Vec_u8_pointers_behind_refs_closure8(runs2, (r2: rs.ByteVec): boolean => { diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index 5cd12523e..eda948ec7 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2511,8 +2511,8 @@ fn cell_behind(r: &Option>, how: u8) -> u32 { } } -/// Raw pointers from references: smallvec's `Drain` and `retain`, over values JS copies, a -/// library type, and an `Option`; pointers from a reference the body borrowed, which move +/// Raw pointers from references: smallvec's `Drain` and `retain`, over a number, library +/// types, and an `Option`; pointers from a reference the body borrowed, which move /// within the array it borrowed from; from references the body is given, to a number, to an /// `Option` of a cell, and to a `Vec`, read and written whole; and from references to an /// `Arc`'s value, measured against each other. From 694c6e18b980ea7afebf05f99ae0106fb51e3eb9 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 22:04:30 -0800 Subject: [PATCH 10/29] chore(ts): regenerate the pointer snapshots after merging main Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/pointers/expected/lib.ts | 8 +- .../lang/pointers/expected/pointers.ts | 321 +++++++++++++++++- 2 files changed, 324 insertions(+), 5 deletions(-) diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index 0f3ef1956..ae9d5ec7d 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -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, genericUnions } 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, arrays, behindRefs, genericUnions } 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"; @@ -302,8 +302,10 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By 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::generic_unions", (_237: rs.u8, data28: rs.ByteSlice, out28: rs.ByteVec): undefined => { - genericUnions(data28, out28); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::behind_refs", (_237: rs.u8, data28: rs.ByteSlice, out28: rs.ByteVec): undefined => { + behindRefs(data28, out28); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::generic_unions", (_238: rs.u8, data29: rs.ByteSlice, out29: rs.ByteVec): undefined => { + genericUnions(data29, out29); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index a059e4922..f6918860e 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -2,14 +2,15 @@ import * as rs from "./rs.ts"; import { copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_Twin, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; -import { then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; -import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$173j2gr as option_mapOr$173j2gr, filter$0wvwd70 } from "./core/option.ts"; +import { thenSome$1gvhopg, then$01qpu7s, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; +import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; import type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; import type * as ByCall from "./pointers.ByCall.ts"; import * as ByClosure from "./pointers.ByClosure.ts"; import type * as ByObject from "./pointers.ByObject.ts"; import * as Counted from "./pointers.Counted.ts"; +import * as Drained from "./pointers.Drained.ts"; import type * as Duo from "./pointers.Duo.ts"; import type * as Either from "./pointers.Either.ts"; import type * as Enclosed from "./pointers.Enclosed.ts"; @@ -2980,6 +2981,322 @@ export function lent(n: rs.u8): rs.ByteVec { return o; } +/** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ +export function drained$Option_u16(v: rs.Vec, bits: rs.u8): rs.Vec { + let next!: rs.u16 | undefined | rs.None; + const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const out: rs.Vec = new rs.Vec(); + let i: rs.i32 = 0; + while (true) { + if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { + next = rs.iterToOption(Drained.next$iter_Iterator_Option_u16(iter), rs.None); + } else { + next = Drained.nextBack$iter_DoubleEndedIterator_Option_u16(iter); + } + if (next !== rs.None) { + out.items.push(next); + i = rs.i32Add(i, 1); + } else { + return out; + } + } +} + +/** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ +export function drained$String(v: rs.Vec, bits: rs.u8): rs.Vec { + let next!: rs.String | undefined; + const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const out: rs.Vec = new rs.Vec(); + let i: rs.i32 = 0; + while (true) { + if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { + next = rs.iterToOption(Drained.next$iter_Iterator_String(iter)); + } else { + next = Drained.nextBack$iter_DoubleEndedIterator_String(iter); + } + if (next !== undefined) { + out.items.push(next); + i = rs.i32Add(i, 1); + } else { + return out; + } + } +} + +/** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ +export function drained$Vec_u8(v: rs.Vec, bits: rs.u8): rs.Vec { + let next!: rs.ByteVec | undefined; + const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const out: rs.Vec = new rs.Vec(); + let i: rs.i32 = 0; + while (true) { + if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { + next = rs.iterToOption(Drained.next$iter_Iterator_Vec_u8(iter)); + } else { + next = Drained.nextBack$iter_DoubleEndedIterator_Vec_u8(iter); + } + if (next !== undefined) { + out.items.push(next); + i = rs.i32Add(i, 1); + } else { + return out; + } + } +} + +/** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ +export function drained$u32(v: rs.Vec, bits: rs.u8): rs.Vec { + let next!: rs.u32 | undefined; + const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const out: rs.Vec = new rs.Vec(); + let i: rs.i32 = 0; + while (true) { + if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { + next = rs.iterToOption(Drained.next$iter_Iterator_u32(iter)); + } else { + next = Drained.nextBack$iter_DoubleEndedIterator_u32(iter); + } + if (next !== undefined) { + out.items.push(next); + i = rs.i32Add(i, 1); + } else { + return out; + } + } +} + +/** + * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then + * drops or moves to the front. + */ +export function retained$String_pointers_behind_refs_closure6(v: rs.Vec, keep: (a0: rs.String) => boolean): void { + let _10!: rs.usize; + const len: rs.usize = v.items.length; + const p: rs.Place = rs.vecAsPtr(v, 12); + let write: rs.usize = 0; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = len; + while ((_10 = iterStart++) < iterEnd) { + const _15: rs.Place = rs.ptrAdd(p, _10); + if (keep(_15.v)) { + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); + write = rs.usizeAdd(write, 1); + } + } + rs.vecSplitOff(v, write); +} + +/** + * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then + * drops or moves to the front. + */ +export function retained$Vec_u8_pointers_behind_refs_closure8(v: rs.Vec, keep: (a0: rs.ByteVec) => boolean): void { + let _10!: rs.usize; + const len: rs.usize = v.items.length; + const p: rs.Place = rs.vecAsPtr(v, 12); + let write: rs.usize = 0; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = len; + while ((_10 = iterStart++) < iterEnd) { + const _15: rs.Place = rs.ptrAdd(p, _10); + if (keep(_15.v)) { + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); + write = rs.usizeAdd(write, 1); + } + } + rs.vecSplitOff(v, write); +} + +/** + * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then + * drops or moves to the front. + */ +export function retained$u32_pointers_behind_refs_closure4(v: rs.Vec, keep: (a0: rs.Place) => boolean): void { + let _10!: rs.usize; + const len: rs.usize = v.items.length; + const p: rs.Place = rs.vecAsPtr(v, 4); + let write: rs.usize = 0; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = len; + while ((_10 = iterStart++) < iterEnd) { + const _15: rs.Place = rs.ptrAdd(p, _10); + if (keep(_15)) { + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); + write = rs.usizeAdd(write, 1); + } + } + rs.vecSplitOff(v, write); +} + +/** The length of the `Vec` a pointer from a reference the body is given points at. */ +export function vecLen(v: rs.ByteVec): rs.usize { + return { v }.v.length; +} + +/** + * `with` written whole through a pointer from a `&mut` the body is given, made each way + * std makes one, which its caller's `Vec` holds after. + */ +export function refilled(v: rs.ByteVec, with_: rs.ByteVec, how: rs.u8): void { + let p!: rs.Place; + const _5: rs.u8 = rs.u8Rem(how, 4); + if (_5 === 0) { + const target4 = v; + p = rs.place(() => target4, (value4) => Object.assign(target4, value4)); + } else if (_5 === 1) { + const target3 = v; + p = rs.place(() => target3, (value3) => Object.assign(target3, value3)); + } else if (_5 === 2) { + const target2 = v; + p = rs.place(() => target2, (value2) => Object.assign(target2, value2)); + } else { + const target = v; + p = rs.place(() => target, (value) => Object.assign(target, value)); + } + p.v = with_; + rs.byteVecPush(p.v, how); +} + +/** + * The value a pointer from a shared reference the body is given points at, made each way + * std makes one. + */ +export function behind(r: rs.u32, how: rs.u8): rs.u32 { + let p!: rs.Place; + const _4: rs.u8 = rs.u8Rem(how, 3); + if (_4 === 0) { + p = { v: r }; + } else if (_4 === 1) { + p = { v: r }; + } else { + p = { v: r }; + } + return p.v; +} + +/** + * Whether the `Option` a pointer from a shared reference the body is given points at + * holds a cell, and the number in it, read through the pointer made each way std makes + * one. + */ +export function cellBehind(r: rs.Cell | undefined, how: rs.u8): rs.u32 { + let p!: rs.Place | undefined>; + if (rs.u8Rem(how, 2) === 0) { + p = { v: r }; + } else { + p = { v: r }; + } + const _6: rs.Cell | undefined = p.v; + if (_6 === undefined) { + return 0; + } + return rs.u32Add(_6.v, 1); +} + +/** + * Raw pointers from references: smallvec's `Drain` and `retain`, over a number, library + * types, and an `Option`; pointers from a reference the body borrowed, which move + * within the array it borrowed from; from references the body is given, to a number, to an + * `Option` of a cell, and to a `Vec`, read and written whole; and from references to an + * `Arc`'s value, measured against each other. + */ +export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { + let _112!: rs.usize; + const bits: rs.u8 = option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0); + const nums: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_2: rs.u8): rs.u32 => { + return rs.u32Mul(_2 as rs.u32, 3); + }), undefined, 4, true); + rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(drained$u32(rs.vecFrom(nums.items.slice()), bits).items), (_23: rs.u32): rs.u8 => { + return _23 & 0xff; + }), undefined, true); + const words: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b3: rs.u8): rs.String => { + return rs.fmtToString((f: rs.Formatter) => rs.u8Display(b3, f), 0); + }), undefined, 12, true); + for (const _25 of drained$String(rs.vecFrom(words.items.map((a0: rs.String): rs.String => { + return rs.stringClone(a0); + })), bits).items) { + rs.iterExtendBytes(out, rs.strBytes(_25.v), undefined, true); + } + const runs: rs.Vec = rs.iterToVec(rs.iterMap(rs.byteSliceChunks(data, 3), (a02: rs.ByteSlice): rs.ByteVec => { + return rs.byteSliceToByteVec(a02); + }), undefined, 12, true); + for (const r of drained$Vec_u8(rs.vecFrom(runs.items.map((a03: rs.ByteVec): rs.ByteVec => { + return rs.byteVecFrom(rs.byteVecView(a03)); + })), bits).items) { + rs.byteVecPush(out, r.length & 0xff); + rs.iterExtendBytes(out, rs.byteVecIntoIter(r), undefined, true); + } + for (const _56 of drained$Option_u16(rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_24: rs.u8): rs.u16 | undefined => { + const b4: rs.u8 = _24; + return thenSome$1gvhopg(rs.u8Rem(b4, 3) !== 0, b4 as rs.u16); + }), undefined, 4, true), bits).items) { + rs.iterExtendBytes(out, rs.iterSlice(option_mapOr$0smr0aj(_56, Uint8Array.of(255, 255), (a04: rs.u16): Uint8Array => { + return rs.u16ToBeBytes(a04); + })), undefined, true); + } + const nums2: rs.Vec = nums; + retained$u32_pointers_behind_refs_closure4(nums2, (n2: rs.Place): boolean => { + n2.v = rs.u32Add(n2.v, 1); + return rs.u32Rem(n2.v, 2) === 0; + }); + rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(nums2.items), (_26: rs.u32): rs.u8 => { + return _26 & 0xff; + }), undefined, true); + const words2: rs.Vec = words; + retained$String_pointers_behind_refs_closure6(words2, (w2: rs.String): boolean => { + rs.stringPush(w2, 33); + return rs.usizeRem(w2.len, 2) === 0; + }); + rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(words2.items), (w3: rs.String): rs.Iter => { + return rs.strBytes(w3.v); + }, undefined, true), "next"); + const runs2: rs.Vec = runs; + retained$Vec_u8_pointers_behind_refs_closure8(runs2, (r2: rs.ByteVec): boolean => { + rs.byteVecPush(r2, 7); + return rs.u8Rem(r2.data[rs.idx(r2, 0)], 2) === 0; + }); + rs.iterExtendBytes(out, rs.iterFlatten(rs.iterSlice(runs2.items), (value: rs.ByteVec) => rs.iterSlice(rs.byteVecView(value)), true), "next"); + const nums3: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_27: rs.u8): rs.u32 => { + return _27 as rs.u32; + }), undefined, 4, true); + if (rs.sliceFrom(nums3.items).length >= 2) { + const first: rs.Place = rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 0)); + rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, rs.usizeSub(nums3.items.length, 1))), first) & 0xff); + const _109: rs.usize = nums3.items.length; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = _109; + while ((_112 = iterStart++) < iterEnd) { + rs.byteVecPush(out, rs.ptrAdd(first, _112).v & 0xff); + } + rs.byteVecPush(out, rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v & 0xff); + rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v = 9; + rs.byteVecPush(out, nums3.items[rs.idx(nums3.items, 0)] & 0xff); + } + const v: rs.ByteVec = rs.byteSliceToByteVec(data); + rs.byteVecPush(out, vecLen(v) & 0xff); + const _143: rs.ByteVec = rs.iterToByteVec(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), undefined, true); + refilled(v, _143, bits); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); + const iter5: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); + while (true) { + const _153: [rs.usize, rs.u8] | rs.Done = iter5.next(); + if (_153 === rs.DONE) { + break; + } + const i2: rs.usize = _153[0]; + const b: rs.u8 = _153[1]; + rs.byteVecPush(out, behind(b as rs.u32, i2 & 0xff) & 0xff); + rs.byteVecPush(out, cellBehind(then$01qpu7s(rs.u8Rem(b, 3) !== 0, (): rs.Cell => { + return new rs.Cell(b as rs.u32); + }), b) & 0xff); + } + const arc: rs.Arc = new rs.Arc(bits as rs.u32); + const q: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); + rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arcRaw(arc, rs.arcPlace(arc), "Arc"), q) & 0xff); + rs.byteVecPush(out, q.v & 0xff); + arc[Symbol.dispose](); +} + /** * Each instance of a union over a type parameter: written as one field, in part through * another and through a reference into it, and whole as a third, read as each, and copied From f45cfbcada9a13ab5afad4cd41dafa7c3fa42678 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 22:10:54 -0800 Subject: [PATCH 11/29] quest: recount the census after merging main Co-Authored-By: Claude Opus 5.5 --- quest/m2/ts-model/README.md | 10 +++++----- quest/m3/ts-unsafe/README.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index f54823ec8..2e6f162e2 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -42,15 +42,15 @@ 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-07: 45 refusals. `Timestamp` alone refuses nothing, and +The census at the pin on 2026-11-07: 46 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 -refuses for good, and smallvec's union over a type parameter and raw parts (4) wait on -[unsafe](/quest/m3/ts-unsafe/README.md), so moq rewrites or drops them, as it does the -`unsafe` slice of `model/frame.rs`'s `Filled`. +crossbeam-epoch, whose tagged pointers and inline closures (33 refusals) translation +refuses for good, and smallvec's union over a type parameter, its `Array::Item`, and raw +parts (5) wait on [unsafe](/quest/m3/ts-unsafe/README.md), so moq rewrites or drops them, +as it 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 (moq-dev/rust2#390): the refusals no quest owned became the children below. ✅ arrayvec's diff --git a/quest/m3/ts-unsafe/README.md b/quest/m3/ts-unsafe/README.md index 33d06aef6..b2ec2e79c 100644 --- a/quest/m3/ts-unsafe/README.md +++ b/quest/m3/ts-unsafe/README.md @@ -66,7 +66,7 @@ construct still open at `b3df54b6c`, all in m3: [#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-07: model 45, kio 72, moq-net 347 refusals. +Census at the moq pin on 2026-11-07: model 46, kio 72, moq-net 348 refusals. Public API: the translator's output. From e58dcda302c732fc2d0ef7cbec2fe41edb2dbed4 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 22:35:36 -0800 Subject: [PATCH 12/29] fix(ts): move a copy out of a pointer from a &mut to an object A raw pointer from a `&mut` to a library object writes a whole value into that object in place, so `ptr::replace`, `ptr::read` before a write, `ptr::copy`, and `ptr::swap` returned the object the write then changed. Such a pointer is now an `rs.placeIn`, whose reads that move the value out copy it, as `replaceIn` does for `mem::replace`. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 2 +- .../pointers/expected/pointers.Drained.ts | 8 +- .../lang/pointers/expected/pointers.ts | 86 ++++++++++++++----- ts/fixtures/lang/pointers/src/pointers.rs | 39 ++++++++- ts/runtime/rs.ts | 47 +++++++++- ts/src/lower/mir/call.rs | 2 +- ts/src/lower/mir/mod.rs | 10 +++ ts/src/lower/mir/place.rs | 24 ++---- ts/src/lower/mir/rvalue.rs | 22 ++--- ts/src/lower/pointer.rs | 10 +-- 10 files changed, 182 insertions(+), 68 deletions(-) diff --git a/ts/README.md b/ts/README.md index 2bd2d5f19..941ef2681 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: 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` | `rs.Cow`: 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`, `Reverse`, `NonZero`, `AssertUnwindSafe` | 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`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | 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` 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or writing a whole value into the object a `&mut` is in place. 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` | 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` 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place, which `ptr::read`, `replace`, `swap`, and `copy` move out a copy of. 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`: `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`, 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 | diff --git a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts index da114c70c..66be08b9e 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts @@ -19,13 +19,13 @@ export function next$iter_Iterator_Option_u16(self: Drained) export function next$iter_Iterator_String(self: Drained): rs.String | rs.Done { return rs.iterFromOption(option_map$1lzqy5c(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.String): rs.String => { - return { v: r }.v; + return r; })); } export function next$iter_Iterator_Vec_u8(self: Drained): rs.ByteVec | rs.Done { return rs.iterFromOption(option_map$1h9tk16(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.ByteVec): rs.ByteVec => { - return { v: r }.v; + return r; })); } @@ -43,13 +43,13 @@ export function nextBack$iter_DoubleEndedIterator_Option_u16(self: Drained): rs.String | undefined { return option_map$1lzqy5c(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.String): rs.String => { - return { v: r }.v; + return r; }); } export function nextBack$iter_DoubleEndedIterator_Vec_u8(self: Drained): rs.ByteVec | undefined { return option_map$1h9tk16(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.ByteVec): rs.ByteVec => { - return { v: r }.v; + return r; }); } diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index f6918860e..a6cce8b8f 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -3141,22 +3141,50 @@ export function refilled(v: rs.ByteVec, with_: rs.ByteVec, how: rs.u8): void { let p!: rs.Place; const _5: rs.u8 = rs.u8Rem(how, 4); if (_5 === 0) { - const target4 = v; - p = rs.place(() => target4, (value4) => Object.assign(target4, value4)); + p = rs.placeIn(v); } else if (_5 === 1) { - const target3 = v; - p = rs.place(() => target3, (value3) => Object.assign(target3, value3)); + p = rs.placeIn(v); } else if (_5 === 2) { - const target2 = v; - p = rs.place(() => target2, (value2) => Object.assign(target2, value2)); + p = rs.placeIn(v); } else { - const target = v; - p = rs.place(() => target, (value) => Object.assign(target, value)); + p = rs.placeIn(v); } p.v = with_; rs.byteVecPush(p.v, how); } +/** + * What the `Vec` a pointer from a `&mut` the body is given points at held, moved out + * each way std moves one out as `with` is written whole in its place, and then swapped + * with `w` through pointers from `&mut`s to each. + */ +export function replaced(v: rs.ByteVec, w: rs.ByteVec, with_: rs.ByteVec, how: rs.u8): rs.ByteVec { + let old!: rs.ByteVec; + const p: rs.Place = rs.placeIn(v); + const _7: rs.u8 = rs.u8Rem(how, 3); + if (_7 === 0) { + const pointer = p; + const old3 = rs.ptrRead(pointer); + pointer.v = with_; + old = old3; + } else if (_7 === 1) { + const old2: rs.ByteVec = rs.ptrRead(p); + p.v = with_; + old = old2; + } else { + let slot: rs.ByteVec = (undefined as unknown) as rs.ByteVec; + rs.ptrCopy(p, rs.place(() => slot, (value) => slot = value), 1); + p.v = with_; + old = slot; + } + const x = rs.placeIn(v); + const y = rs.placeIn(w); + const old4 = rs.ptrRead(x); + x.v = rs.ptrRead(y); + y.v = old4; + return old; +} + /** * The value a pointer from a shared reference the body is given points at, made each way * std makes one. @@ -3195,13 +3223,14 @@ export function cellBehind(r: rs.Cell | undefined, how: rs.u8): rs.u32 { /** * Raw pointers from references: smallvec's `Drain` and `retain`, over a number, library - * types, and an `Option`; pointers from a reference the body borrowed, which move - * within the array it borrowed from; from references the body is given, to a number, to an - * `Option` of a cell, and to a `Vec`, read and written whole; and from references to an - * `Arc`'s value, measured against each other. + * types, and an `Option`; pointers from a reference the body borrowed, which move within + * the array it borrowed from; from references the body is given, to a number, to an + * `Option` of a cell, and to a `Vec`, read, written, moved out of, and swapped whole; and + * from references to an `Arc`'s value, measured against each other. */ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { let _112!: rs.usize; + let _152!: rs.u8; const bits: rs.u8 = option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0); const nums: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_2: rs.u8): rs.u32 => { return rs.u32Mul(_2 as rs.u32, 3); @@ -3243,12 +3272,12 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { return _26 & 0xff; }), undefined, true); const words2: rs.Vec = words; - retained$String_pointers_behind_refs_closure6(words2, (w2: rs.String): boolean => { - rs.stringPush(w2, 33); - return rs.usizeRem(w2.len, 2) === 0; + retained$String_pointers_behind_refs_closure6(words2, (w3: rs.String): boolean => { + rs.stringPush(w3, 33); + return rs.usizeRem(w3.len, 2) === 0; }); - rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(words2.items), (w3: rs.String): rs.Iter => { - return rs.strBytes(w3.v); + rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(words2.items), (w4: rs.String): rs.Iter => { + return rs.strBytes(w4.v); }, undefined, true), "next"); const runs2: rs.Vec = runs; retained$Vec_u8_pointers_behind_refs_closure8(runs2, (r2: rs.ByteVec): boolean => { @@ -3277,14 +3306,27 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { const _143: rs.ByteVec = rs.iterToByteVec(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), undefined, true); refilled(v, _143, bits); rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); - const iter5: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); + let iterStart2: rs.u8 = 0; + const iterEnd2: rs.u8 = 3; + while ((_152 = iterStart2++) < iterEnd2) { + const how: rs.u8 = _152; + const w2: rs.ByteVec = rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b6: rs.u8): rs.u8 => { + return b6 ^ how; + }), undefined, true); + const _164: rs.ByteVec = rs.byteVecOf(bits, rs.usizeAdd(how as rs.usize, 1)); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(replaced(v, w2, _164, how))), undefined, true); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(w2)), undefined, true); + rs.byteVecPush(out, 255); + } + const iter6: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _153: [rs.usize, rs.u8] | rs.Done = iter5.next(); - if (_153 === rs.DONE) { + const _179: [rs.usize, rs.u8] | rs.Done = iter6.next(); + if (_179 === rs.DONE) { break; } - const i2: rs.usize = _153[0]; - const b: rs.u8 = _153[1]; + const i2: rs.usize = _179[0]; + const b: rs.u8 = _179[1]; rs.byteVecPush(out, behind(b as rs.u32, i2 & 0xff) & 0xff); rs.byteVecPush(out, cellBehind(then$01qpu7s(rs.u8Rem(b, 3) !== 0, (): rs.Cell => { return new rs.Cell(b as rs.u32); diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index 2983b01de..640767f26 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2487,6 +2487,29 @@ fn refilled(v: &mut Vec, with: Vec, how: u8) { unsafe { (*p).push(how) }; } +/// What the `Vec` a pointer from a `&mut` the body is given points at held, moved out +/// each way std moves one out as `with` is written whole in its place, and then swapped +/// with `w` through pointers from `&mut`s to each. +fn replaced(v: &mut Vec, w: &mut Vec, with: Vec, how: u8) -> Vec { + let p = ptr::from_mut(v); + let old = match how % 3 { + 0 => unsafe { ptr::replace(p, with) }, + 1 => { + let old = unsafe { ptr::read(p) }; + unsafe { ptr::write(p, with) }; + old + } + _ => { + let mut slot = MaybeUninit::>::uninit(); + unsafe { ptr::copy_nonoverlapping(p, slot.as_mut_ptr(), 1) }; + unsafe { ptr::write(p, with) }; + unsafe { slot.assume_init() } + } + }; + unsafe { ptr::swap(ptr::from_mut(v), ptr::from_mut(w)) }; + old +} + /// The value a pointer from a shared reference the body is given points at, made each way /// std makes one. fn behind(r: &u32, how: u8) -> u32 { @@ -2513,10 +2536,10 @@ fn cell_behind(r: &Option>, how: u8) -> u32 { } /// Raw pointers from references: smallvec's `Drain` and `retain`, over a number, library -/// types, and an `Option`; pointers from a reference the body borrowed, which move -/// within the array it borrowed from; from references the body is given, to a number, to an -/// `Option` of a cell, and to a `Vec`, read and written whole; and from references to an -/// `Arc`'s value, measured against each other. +/// types, and an `Option`; pointers from a reference the body borrowed, which move within +/// the array it borrowed from; from references the body is given, to a number, to an +/// `Option` of a cell, and to a `Vec`, read, written, moved out of, and swapped whole; and +/// from references to an `Arc`'s value, measured against each other. pub fn behind_refs(data: &[u8], out: &mut Vec) { let bits = data.first().copied().unwrap_or(0); let nums: Vec = data.iter().map(|&b| u32::from(b) * 3).collect(); @@ -2574,6 +2597,14 @@ pub fn behind_refs(data: &[u8], out: &mut Vec) { out.push(vec_len(&v) as u8); refilled(&mut v, data.iter().rev().copied().collect(), bits); out.extend(&v); + for how in 0..3 { + let mut w: Vec = data.iter().map(|b| b ^ how).collect(); + let old = replaced(&mut v, &mut w, vec![bits; usize::from(how) + 1], how); + out.extend(&old); + out.extend(&v); + out.extend(&w); + out.push(0xff); + } for (i, &b) in data.iter().enumerate() { out.push(behind(&u32::from(b), i as u8) as u8); let cell = (b % 3 != 0).then(|| Cell::new(u32::from(b))); diff --git a/ts/runtime/rs.ts b/ts/runtime/rs.ts index f43eeecb1..70a949afd 100644 --- a/ts/runtime/rs.ts +++ b/ts/runtime/rs.ts @@ -141,6 +141,43 @@ export function place(get: () => T, set: (value: T) => void): Place { return new Accessed(get, set); } +/** + * A place that is the object `target`, which a write of a whole value lands in, as + * `replaceIn` writes: a raw pointer from a `&mut` to an object, which others may hold. + */ +class WrittenIn implements Place { + readonly target: T; + /** The levels of arrays whose items a copy of `target` copies too, as `objectCopy`'s. */ + readonly depth: number; + + constructor(target: T, depth: number) { + this.target = target; + this.depth = depth; + } + + get v(): T { + return this.target; + } + + set v(value: T) { + writeIn(this.target, value); + } +} + +/** The raw pointer from a `&mut` to `target`, an object, as `WrittenIn` is. */ +export function placeIn(target: T, depth = 0): Place { + return new WrittenIn(target, depth); +} + +/** + * What a read through `p` moves out, as `ptr::read` does: what it holds, or a copy of the + * object a `placeIn` is, which a write through it would change. + */ +export function ptrRead(p: Place): T { + const home = ptrHome(p); + return home instanceof WrittenIn ? (objectCopy(home.target, home.depth) as T) : p.v; +} + // Raw pointers /** A null raw pointer, of any type: nothing reads through it. */ @@ -445,6 +482,9 @@ export function ptrCopy(src: Place, dst: Place, n: number, copy?: (valu return; } const values = Array.from({ length: n }, (_, i) => elemGet(from.array, from.index + i)); + // What a `placeIn` holds changes with the next write through it. + const home = ptrHome(from); + if (home instanceof WrittenIn) values[0] = objectCopy(home.target, home.depth) as T; // Memory nothing wrote yet is copied as it is, which no value's copy can read. for (let i = 0; i < n; i++) elemSet(to.array, to.index + i, copy && values[i] !== undefined ? copy(values[i]) : values[i]); } @@ -565,9 +605,14 @@ export function ptrWrite(p: T, value: T): void { */ export function replaceIn(p: T, value: T, depth = 0): T { const old = objectCopy(p, depth); + writeIn(p, value); + return old; +} + +/** `value`'s fields, or its items for a JS array, moved into `p`. */ +function writeIn(p: T, value: T): void { if (Array.isArray(p)) arrayAssign(p, value as unknown[]); else ptrWrite(p, value); - return old; } /** diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 8d0610232..d749adccc 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -1834,7 +1834,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { /// How many levels of arrays of objects a write may land in `ty` nests, whose items a /// copy of a view of one copies too, unlike references, which move as they are: the /// runtime's `depth`, if any. - fn copy_depth(&self, ty: Ty<'tcx>) -> Option { + pub(super) fn copy_depth(&self, ty: Ty<'tcx>) -> Option { let mut depth = 0; let mut at = ty; while let ty::Array(elem, _) = at.kind() diff --git a/ts/src/lower/mir/mod.rs b/ts/src/lower/mir/mod.rs index 45dc195cb..32a536015 100644 --- a/ts/src/lower/mir/mod.rs +++ b/ts/src/lower/mir/mod.rs @@ -3685,6 +3685,16 @@ fn subst(e: &mut Expr, name: &str, value: &Expr) -> bool { { *e = value.clone(); } + // A read through a pointer made here of a shared reference is the value it holds. + if let Expr::Call(f, args) = &*e + && let (Expr::Rs(read), [p]) = (&**f, args.as_slice()) + && read == "ptrRead" + && let Expr::Object(members) = step::peel(p) + && let [(ts::Key::Name(v), value)] = members.as_slice() + && v == "v" + { + *e = value.clone(); + } // The elements of a slice of all of an array are the array, and a copy of them its copy. if let Expr::Call(f, args) = &*e && let (Expr::Rs(read), [slice]) = (&**f, args.as_slice()) diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 252fa63d9..f0b7e266b 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -823,8 +823,15 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { matches!(self.mir.local_decls[place.local].ty.kind(), ty::RawPtr(..)) && !self.box_pointer(place.local); let e = match raw { true => Expr::rs("ptrWrite", vec![target, value]), - false => match self.write_into(target, value, pointee) { - Some(e) => e, + false => match self.b.t.write_in(pointee) { + Some(ObjectKind::Bytes) => target.member("set").call(vec![value]), + // A boxed slice is a JS array, whose length a write changes, unlike a + // tuple's or an array's. + Some(ObjectKind::Array) if self.b.t.erased(pointee).is_slice() => { + Expr::rs("arrayAssign", vec![target, value]) + } + Some(_) if self.b.t.moves_private(pointee) => Expr::rs("ptrWrite", vec![target, value]), + Some(_) => Expr::ident("Object").member("assign").call(vec![target, value]), None => self.err(span, format!("an assignment through a reference to `{pointee}`")), }, }; @@ -837,19 +844,6 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { out } - /// The write of `value`, a whole `pointee`, into `target`, the object a reference to it - /// is, in place, if JS can write one so. - pub(super) fn write_into(&self, target: Expr, value: Expr, pointee: Ty<'tcx>) -> Option { - Some(match self.b.t.write_in(pointee)? { - ObjectKind::Bytes => target.member("set").call(vec![value]), - // A boxed slice is a JS array, whose length a write changes, unlike a tuple's or - // an array's. - ObjectKind::Array if self.b.t.erased(pointee).is_slice() => Expr::rs("arrayAssign", vec![target, value]), - _ if self.b.t.moves_private(pointee) => Expr::rs("ptrWrite", vec![target, value]), - _ => Expr::ident("Object").member("assign").call(vec![target, value]), - }) - } - /// `target = value`, where `target` may be a struct's byte slice field held as its /// parts: each of its fields gets its part, in order, read before any is written where /// a part reads a field written before it. diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 2918fd1b3..3513f9686 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -280,8 +280,8 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { /// `rs.Place`, where `r` is no `rs.Place` itself but the value: the place the body shows /// `r` borrowed, as [`Self::borrowed_pointer`] finds it. Otherwise it is a place of its /// own, as `ptr::from_ref` makes: of the value, where `r` is shared and nothing may write - /// a whole value through the pointer, and else writing a whole value into the object `r` - /// is in place, as `*r = v` does. + /// a whole value through the pointer, and else an `rs.placeIn` writing a whole value into + /// the object `r` is in place, as `*r = v` does, which a read moving out copies. pub(super) fn behind_reference(&mut self, r: Place<'tcx>, span: Span, out: &mut Vec) -> Expr { let tcx = self.tcx; if let Some(p) = self.borrowed_pointer(r, span, out) { @@ -303,23 +303,15 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { if shared && (tagged || ty.is_freeze(tcx, env)) { return Expr::Object(vec![(Key::Name("v".into()), value)]); } - let target = self.b.spill(value, "target", out); - let written = self.b.fresh("value"); - let Some(write) = self.write_into(target.clone(), Expr::ident(&written), ty) else { + if self.b.t.write_in(ty).is_none() { return self.err( span, format!("a raw pointer to `{ty}` behind a reference, which JS can't write a whole value into in place"), ); - }; - let get = Expr::Lambda(Vec::new(), Box::new(target)); - let set = Expr::Lambda( - vec![Param { - name: written, - ty: None, - }], - Box::new(write), - ); - Expr::rs("place", vec![get, set]) + } + let mut made = vec![value]; + made.extend(self.copy_depth(ty)); + Expr::rs("placeIn", made) } /// The raw pointer to the place the body shows the reference `r` borrowed, if it does: diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 340d6e2e9..e046c1e9b 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -1112,11 +1112,11 @@ pub fn raw_pointee<'tcx>(tcx: ty::TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option fn read<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, ty: Ty<'tcx>, span: Span) -> Expr { let value = match b.t.pointee(ty) { // The value a pointer made here of a shared reference holds. - Pointee::Place => match p { - Expr::Object(mut members) if matches!(members.as_slice(), [(Key::Name(v), _)] if v == "v") => { - members.pop().expect("one member").1 - } - p => p.member("v"), + Pointee::Place => match p.unlocated() { + Expr::Object(members) if let [(Key::Name(v), value)] = members.as_slice() && v == "v" => value.clone(), + // It may be an `rs.placeIn`, whose object the next write through it changes. + _ if b.t.write_in(ty).is_some() => Expr::rs("ptrRead", vec![p]), + _ => p.member("v"), }, Pointee::Object => p, Pointee::Array => { From 0916afe59c7ca6716fe25cf6e3fe69758ec5ab34 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 22:54:51 -0800 Subject: [PATCH 13/29] chore(ts): regenerate the snapshots after merging main Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/elements/expected/spare.ts | 4 ++-- ts/fixtures/lang/pointers/expected/lib.ts | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ts/fixtures/lang/elements/expected/spare.ts b/ts/fixtures/lang/elements/expected/spare.ts index 33f182bbe..d92a34680 100644 --- a/ts/fixtures/lang/elements/expected/spare.ts +++ b/ts/fixtures/lang/elements/expected/spare.ts @@ -397,7 +397,7 @@ export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { let iterStart2: rs.usize = at2; const iterEnd2: rs.usize = end2; while ((_155 = iterStart2++) < iterEnd2) { - put$spare_Item_String(rs.ptrAdd(p2, _155).v, out); + put$spare_Item_String(rs.ptrRead(rs.ptrAdd(p2, _155)), out); } rs.ptrCopy(rs.ptrAdd(p2, end2), rs.ptrAdd(p2, at2), rs.usizeSub(len, end2)); rs.vecSetLen(v, rs.usizeSub(len, rs.usizeSub(end2, at2)), 12, (): rs.String => { @@ -416,7 +416,7 @@ export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { const iterEnd3: rs.usize = len; while ((_184 = iterStart3++) < iterEnd3) { const i4: rs.usize = _184; - const x: rs.String = rs.ptrAdd(p3, i4).v; + const x: rs.String = rs.ptrRead(rs.ptrAdd(p3, i4)); if (rs.usizeRem(rs.usizeAdd(i4, n as rs.usize), 3) !== 0) { rs.ptrAdd(p3, kept).v = x; kept = rs.usizeAdd(kept, 1); diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index e0bf9590c..261f55ad2 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -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, arrayCasts, genericUnions } 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, arrays, behindRefs, arrayCasts, genericUnions } 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"; @@ -302,10 +302,12 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By 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::array_casts", (_237: rs.u8, data28: rs.ByteSlice, out28: rs.ByteVec): undefined => { - arrayCasts(data28, out28); - }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::generic_unions", (_238: rs.u8, data29: rs.ByteSlice, out29: rs.ByteVec): undefined => { - genericUnions(data29, out29); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::behind_refs", (_237: rs.u8, data28: rs.ByteSlice, out28: rs.ByteVec): undefined => { + behindRefs(data28, out28); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::array_casts", (_238: rs.u8, data29: rs.ByteSlice, out29: rs.ByteVec): undefined => { + arrayCasts(data29, out29); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::generic_unions", (_239: rs.u8, data30: rs.ByteSlice, out30: rs.ByteVec): undefined => { + genericUnions(data30, out30); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } From 9bfcf2785ac3c06785330d5c3cd8726c264e1128 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 22:58:10 -0800 Subject: [PATCH 14/29] style(ts): format Co-Authored-By: Claude Opus 5.5 --- ts/src/lower/pointer.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 8615499e0..63ae9914c 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -1132,7 +1132,12 @@ fn read<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, ty: Ty<'tcx>, span: Span) -> let value = match b.t.pointee(ty) { // The value a pointer made here of a shared reference holds. Pointee::Place => match p.unlocated() { - Expr::Object(members) if let [(Key::Name(v), value)] = members.as_slice() && v == "v" => value.clone(), + Expr::Object(members) + if let [(Key::Name(v), value)] = members.as_slice() + && v == "v" => + { + value.clone() + } // It may be an `rs.placeIn`, whose object the next write through it changes. _ if b.t.write_in(ty).is_some() => Expr::rs("ptrRead", vec![p]), _ => p.member("v"), From 28c5450337e9bc35902ad39f5a9d2617e7cccfa0 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 06:46:19 -0800 Subject: [PATCH 15/29] fix(ts): copy every value a pointer read moves out, library objects included ptr::read, replace, swap, and copy now copy each object they move out that the crate writes into in place, at any depth, a library object such as a Vec one object deep and one with interior mutability always, by extending the struct copies in copy.rs. That replaces the runtime ptrRead and WrittenIn check, which missed pointers from shared references, casts to MaybeUninit and ManuallyDrop, and library objects inside an Option, tuple, or struct. From and Into take the pointer shortcuts only for std's reference-to-NonNull impl, and Into of a reference to an Arc's value is its one place. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 2 +- ts/fixtures/lang/conversions/expected/lib.ts | 14 +- ts/fixtures/lang/elements/expected/lib.ts | 4 + ts/fixtures/lang/elements/expected/spare.ts | 12 +- ts/fixtures/lang/locals/expected/locals.ts | 2 +- .../lang/pointers/expected/core/option.ts | 14 + ts/fixtures/lang/pointers/expected/lib.ts | 42 ++- .../pointers/expected/pointers.Drained.ts | 17 +- .../pointers/expected/pointers.Halves32.ts | 11 + .../pointers/expected/pointers.TaggedVec.ts | 11 + .../lang/pointers/expected/pointers.ts | 302 +++++++++++++++--- ts/fixtures/lang/pointers/src/lib.rs | 1 + ts/fixtures/lang/pointers/src/pointers.rs | 155 ++++++++- ts/runtime/rs.ts | 40 +-- ts/src/lower/copy.rs | 21 +- ts/src/lower/mir/call.rs | 10 +- ts/src/lower/mir/mod.rs | 18 +- ts/src/lower/mir/origin.rs | 6 +- ts/src/lower/mir/rvalue.rs | 6 +- ts/src/lower/pointer.rs | 8 +- 20 files changed, 569 insertions(+), 127 deletions(-) create mode 100644 ts/fixtures/lang/pointers/expected/pointers.Halves32.ts create mode 100644 ts/fixtures/lang/pointers/expected/pointers.TaggedVec.ts diff --git a/ts/README.md b/ts/README.md index 181115509..b5b1c65b4 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: 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` | `rs.Cow`: 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`, `Reverse`, `NonZero`, `AssertUnwindSafe` | 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`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | 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` 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place, which `ptr::read`, `replace`, `swap`, and `copy` move out a copy of. 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` | 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` 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `Vec` one object deep, and one with interior mutability, such as a `Cell`, always | | `MaybeUninit` | `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`, 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 | diff --git a/ts/fixtures/lang/conversions/expected/lib.ts b/ts/fixtures/lang/conversions/expected/lib.ts index 0a7051530..a448b3248 100644 --- a/ts/fixtures/lang/conversions/expected/lib.ts +++ b/ts/fixtures/lang/conversions/expected/lib.ts @@ -5,6 +5,18 @@ import { conversions } from "./conversions.ts"; import * as Logged from "./conversions.Logged.ts"; import type * as Named from "./conversions.Named.ts"; +export function copy$String(v: rs.String): rs.String { + return rs.objectCopy(v); +} + +export function copy$Vec_u32(v: rs.Vec): rs.Vec { + return rs.objectCopy(v); +} + +export function copy$Vec_u8(v: rs.ByteVec): rs.ByteVec { + return rs.objectCopy(v); +} + export function copy$array_Option_u16(v: Array): Array { return v.slice(); } @@ -46,7 +58,7 @@ export function copy$conversions_Logged(v: Logged.Logged): Logged.Logged { } export function copy$conversions_Named(v: Named.Named): Named.Named { - return { id: v.id, name: v.name }; + return { id: v.id, name: copy$String(v.name) }; } export function drop$tuple2_conversions_Logged_u8(_1: [Logged.Logged, rs.u8]): void { diff --git a/ts/fixtures/lang/elements/expected/lib.ts b/ts/fixtures/lang/elements/expected/lib.ts index 63ac32a2b..408c432b1 100644 --- a/ts/fixtures/lang/elements/expected/lib.ts +++ b/ts/fixtures/lang/elements/expected/lib.ts @@ -9,6 +9,10 @@ export function copy$Option_tuple2_u8_u8(v: [rs.u8, rs.u8] | undefined): [rs.u8, return v === undefined ? v : copy$tuple2_u8_u8(v); } +export function copy$String(v: rs.String): rs.String { + return rs.objectCopy(v); +} + export function copy$array_Option_tuple2_u8_u8(v: Array<[rs.u8, rs.u8] | undefined>): Array<[rs.u8, rs.u8] | undefined> { return v.map(copy$Option_tuple2_u8_u8); } diff --git a/ts/fixtures/lang/elements/expected/spare.ts b/ts/fixtures/lang/elements/expected/spare.ts index 58d456119..103efd939 100644 --- a/ts/fixtures/lang/elements/expected/spare.ts +++ b/ts/fixtures/lang/elements/expected/spare.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$mem_MaybeUninit_tuple2_u8_u32, copy$tuple2_u8_u32 } from "./lib.ts"; +import { copy$String, copy$mem_MaybeUninit_tuple2_u8_u32, copy$tuple2_u8_u32 } from "./lib.ts"; import { thenSome$1gvhopg } from "./core/bool.ts"; import { mapOr$17z0l8m as option_mapOr$17z0l8m } from "./core/option.ts"; import * as Noted from "./spare.Noted.ts"; @@ -365,7 +365,7 @@ export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { if (_35 === 1) { rs.ptrCopy(rs.vecAsPtr(rs.iterToVec(rs.iterMap(new rs.Range(0, k), (i5: rs.usize): rs.String => { return of$spare_Item_String(n ^ (i5 & 0xff)); - }), undefined, 12, true), 12), rs.ptrAdd(rs.vecAsPtr(v, 12), len), k); + }), undefined, 12, true), 12), rs.ptrAdd(rs.vecAsPtr(v, 12), len), k, copy$String); rs.vecSetLen(v, rs.usizeAdd(len, k), 12, (): rs.String => { return (undefined as unknown) as rs.String; }); @@ -389,7 +389,7 @@ export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { rs.vecReserve(v, 1, 12); const at: rs.usize = rs.usizeRem(n as rs.usize, rs.usizeAdd(len, 1)); const p: rs.Place = rs.ptrAdd(rs.vecAsPtr(v, 12), at); - rs.ptrCopy(p, rs.ptrAdd(p, 1), rs.usizeSub(len, at)); + rs.ptrCopy(p, rs.ptrAdd(p, 1), rs.usizeSub(len, at), copy$String); p.v = of$spare_Item_String(n); rs.vecSetLen(v, rs.usizeAdd(len, 1), 12, (): rs.String => { return (undefined as unknown) as rs.String; @@ -407,9 +407,9 @@ export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { let iterStart2: rs.usize = at2; const iterEnd2: rs.usize = end2; while ((_165 = iterStart2++) < iterEnd2) { - put$spare_Item_String(rs.ptrAdd(p2, _165).v, out); + put$spare_Item_String(copy$String(rs.ptrAdd(p2, _165).v), out); } - rs.ptrCopy(rs.ptrAdd(p2, end2), rs.ptrAdd(p2, at2), rs.usizeSub(len, end2)); + rs.ptrCopy(rs.ptrAdd(p2, end2), rs.ptrAdd(p2, at2), rs.usizeSub(len, end2), copy$String); rs.vecSetLen(v, rs.usizeSub(len, rs.usizeSub(end2, at2)), 12, (): rs.String => { return (undefined as unknown) as rs.String; }); @@ -426,7 +426,7 @@ export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { const iterEnd3: rs.usize = len; while ((_194 = iterStart3++) < iterEnd3) { const i4: rs.usize = _194; - const x: rs.String = rs.ptrAdd(p3, i4).v; + const x: rs.String = copy$String(rs.ptrAdd(p3, i4).v); if (rs.usizeRem(rs.usizeAdd(i4, n as rs.usize), 3) !== 0) { rs.ptrAdd(p3, kept).v = x; kept = rs.usizeAdd(kept, 1); diff --git a/ts/fixtures/lang/locals/expected/locals.ts b/ts/fixtures/lang/locals/expected/locals.ts index 0e82c8bd5..b64244784 100644 --- a/ts/fixtures/lang/locals/expected/locals.ts +++ b/ts/fixtures/lang/locals/expected/locals.ts @@ -295,7 +295,7 @@ export function keys(data: rs.ByteSlice, out: rs.ByteVec): void { } else if (_12 === 1) { const old = key.v; key.v = RIGHT; - rs.byteVecPush(out, get$09fnt4b({ v: old }.v)); + rs.byteVecPush(out, get$09fnt4b(old)); } else if (_12 === 2) { const other: rs.Place>> = { v: RIGHT }; const swapped = key.v; diff --git a/ts/fixtures/lang/pointers/expected/core/option.ts b/ts/fixtures/lang/pointers/expected/core/option.ts index 9244a4c57..67a3f757d 100644 --- a/ts/fixtures/lang/pointers/expected/core/option.ts +++ b/ts/fixtures/lang/pointers/expected/core/option.ts @@ -83,6 +83,13 @@ export function map$070fw7m(self: rs.u32 | undefined, f: (a0: rs.u32) => rs.u32) return f(self); } +export function map$1px2rk4(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.ByteVec): rs.ByteVec | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function mapOr$078vnqn(self: Pair.Pair | undefined, default_: rs.u8, f: (a0: Pair.Pair) => rs.u8): rs.u8 { if (self === undefined) { return default_; @@ -97,6 +104,13 @@ export function mapOr$00zno7m(self: Spot.Spot | undefined, default_: Spot.Spot, return f(copy$pointers_Spot(self)); } +export function mapOr$18adzvj(self: rs.Cell | undefined, default_: rs.u32, _: (a0: rs.Cell) => rs.u32): rs.u32 { + if (self === undefined) { + return default_; + } + return self.v; +} + export function mapOr$173j2gr(self: [rs.u8, rs.Arc] | undefined, default_: rs.u8, f: (a0: [rs.u8, rs.Arc]) => rs.u8): rs.u8 { if (self === undefined) { return default_; diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index 261f55ad2..16f3e9201 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -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, behindRefs, arrayCasts, genericUnions } 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, arrays, behindRefs, movedOut, arrayCasts, genericUnions } 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"; @@ -18,8 +18,21 @@ import type * as Outer from "./pointers.Outer.ts"; import type * as Pair from "./pointers.Pair.ts"; import type * as Paired from "./pointers.Paired.ts"; import type * as Spot from "./pointers.Spot.ts"; +import type * as TaggedVec from "./pointers.TaggedVec.ts"; import type * as Twin from "./pointers.Twin.ts"; +export function copy$Cell_u32(v: rs.Cell): rs.Cell { + return rs.objectCopy(v); +} + +export function copy$Option_Cell_u32(v: rs.Cell | undefined): rs.Cell | undefined { + return v === undefined ? v : copy$Cell_u32(v); +} + +export function copy$Option_Vec_u8(v: rs.ByteVec | undefined): rs.ByteVec | undefined { + return v === undefined ? v : copy$Vec_u8(v); +} + export function copy$Option_pointers_Pair(v: Pair.Pair | undefined): Pair.Pair | undefined { return v === undefined ? v : copy$pointers_Pair(v); } @@ -28,6 +41,14 @@ export function copy$Option_tuple2_u16_u8(v: [rs.u16, rs.u8] | undefined): [rs.u return v === undefined ? v : copy$tuple2_u16_u8(v); } +export function copy$String(v: rs.String): rs.String { + return rs.objectCopy(v); +} + +export function copy$Vec_u8(v: rs.ByteVec): rs.ByteVec { + return rs.objectCopy(v); +} + export function copy$array_array2_u32(v: rs.u32[][]): rs.u32[][] { return v.map(copy$array_u32); } @@ -52,6 +73,13 @@ export function copy$array_u8(v: Uint8Array): Uint8Array { return v.slice(); } +export function copy$mem_MaybeUninit_Vec_u8(v: rs.ByteVec): rs.ByteVec { + if (v === undefined) { + return v; + } + return rs.objectCopy(v); +} + export function copy$mem_MaybeUninit_array_u32(v: rs.u32[]): rs.u32[] { if (v === undefined) { return v; @@ -145,10 +173,18 @@ export function copy$pointers_Spot(v: Spot.Spot): Spot.Spot { return { at: copy$tuple2_u16_u8(v.at), tag: v.tag }; } +export function copy$pointers_TaggedVec(v: TaggedVec.TaggedVec): TaggedVec.TaggedVec { + return { v: copy$Vec_u8(v.v), tag: v.tag }; +} + export function copy$pointers_Twin(v: Twin.Twin): Twin.Twin { return { _0: v._0, _1: v._1 }; } +export function copy$tuple2_Vec_u8_u8(v: [rs.ByteVec, rs.u8]): [rs.ByteVec, rs.u8] { + return [copy$Vec_u8(v[0]), v[1]]; +} + export function copy$tuple2_array2_u32_u8(v: [rs.u32[], rs.u8]): [rs.u32[], rs.u8] { return [copy$array_u32(v[0]), v[1]]; } @@ -190,7 +226,7 @@ export function copy$tuple2_u32_u32(v: [rs.u32, rs.u32]): [rs.u32, rs.u32] { } export function copy$tuple2_u8_String(v: [rs.u8, rs.String]): [rs.u8, rs.String] { - return [v[0], v[1]]; + return [v[0], copy$String(v[1])]; } export function copy$tuple2_u8_sync_Arc_u8(v: [rs.u8, rs.Arc]): [rs.u8, rs.Arc] { @@ -308,6 +344,8 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By arrayCasts(data29, out29); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::generic_unions", (_239: rs.u8, data30: rs.ByteSlice, out30: rs.ByteVec): undefined => { genericUnions(data30, out30); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::moved_out", (_240: rs.u8, data31: rs.ByteSlice, out31: rs.ByteVec): undefined => { + movedOut(data31, out31); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts index 66be08b9e..344d8eaf7 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts @@ -1,6 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; +import { copy$String, copy$Vec_u8 } from "./lib.ts"; import { map$00buaky as option_map$00buaky, map$1lzqy5c as option_map$1lzqy5c, map$1h9tk16 as option_map$1h9tk16, map$070fw7m as option_map$070fw7m } from "./core/option.ts"; /** @@ -13,49 +14,49 @@ export interface Drained { export function next$iter_Iterator_Option_u16(self: Drained): rs.u16 | undefined | rs.Done { return rs.iterFromOption(option_map$00buaky(rs.iterToOption((self.iter as rs.Iter).next(), rs.None), (r: rs.u16 | undefined): rs.u16 | undefined => { - return { v: r }.v; + return r; }), rs.None); } export function next$iter_Iterator_String(self: Drained): rs.String | rs.Done { return rs.iterFromOption(option_map$1lzqy5c(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.String): rs.String => { - return r; + return copy$String(r); })); } export function next$iter_Iterator_Vec_u8(self: Drained): rs.ByteVec | rs.Done { return rs.iterFromOption(option_map$1h9tk16(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.ByteVec): rs.ByteVec => { - return r; + return copy$Vec_u8(r); })); } export function next$iter_Iterator_u32(self: Drained): rs.u32 | rs.Done { return rs.iterFromOption(option_map$070fw7m(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.u32): rs.u32 => { - return { v: r }.v; + return r; })); } export function nextBack$iter_DoubleEndedIterator_Option_u16(self: Drained): rs.u16 | undefined | rs.None { return option_map$00buaky(rs.iterToOption((self.iter as rs.Iter).nextBack(), rs.None), (r: rs.u16 | undefined): rs.u16 | undefined => { - return { v: r }.v; + return r; }); } export function nextBack$iter_DoubleEndedIterator_String(self: Drained): rs.String | undefined { return option_map$1lzqy5c(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.String): rs.String => { - return r; + return copy$String(r); }); } export function nextBack$iter_DoubleEndedIterator_Vec_u8(self: Drained): rs.ByteVec | undefined { return option_map$1h9tk16(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.ByteVec): rs.ByteVec => { - return r; + return copy$Vec_u8(r); }); } export function nextBack$iter_DoubleEndedIterator_u32(self: Drained): rs.u32 | undefined { return option_map$070fw7m(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.u32): rs.u32 => { - return { v: r }.v; + return r; }); } diff --git a/ts/fixtures/lang/pointers/expected/pointers.Halves32.ts b/ts/fixtures/lang/pointers/expected/pointers.Halves32.ts new file mode 100644 index 000000000..2698afc9d --- /dev/null +++ b/ts/fixtures/lang/pointers/expected/pointers.Halves32.ts @@ -0,0 +1,11 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** Two numbers, the second of which a pointer from a reference to them points at. */ +export interface Halves32 { + first: rs.u32; + second: rs.u32; +} + +//# sourceMappingURL=pointers.Halves32.ts.map diff --git a/ts/fixtures/lang/pointers/expected/pointers.TaggedVec.ts b/ts/fixtures/lang/pointers/expected/pointers.TaggedVec.ts new file mode 100644 index 000000000..4c23eb3aa --- /dev/null +++ b/ts/fixtures/lang/pointers/expected/pointers.TaggedVec.ts @@ -0,0 +1,11 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** A `Vec` with a tag, which a pointer to one moves out whole. */ +export interface TaggedVec { + v: rs.ByteVec; + tag: rs.u8; +} + +//# sourceMappingURL=pointers.TaggedVec.ts.map diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 4054d996b..50cc2073d 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -1,9 +1,9 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_Twin, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; +import { copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; import { thenSome$1gvhopg, then$01qpu7s, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; -import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; +import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, map$1px2rk4 as option_map$1px2rk4, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$18adzvj as option_mapOr$18adzvj, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; import type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; import type * as ByCall from "./pointers.ByCall.ts"; @@ -16,6 +16,7 @@ import type * as Either from "./pointers.Either.ts"; import type * as Enclosed from "./pointers.Enclosed.ts"; import type * as Gauge from "./pointers.Gauge.ts"; import type * as Halves from "./pointers.Halves.ts"; +import type * as Halves32 from "./pointers.Halves32.ts"; import * as Held from "./pointers.Held.ts"; import type * as Holder from "./pointers.Holder.ts"; import * as Housed from "./pointers.Housed.ts"; @@ -41,6 +42,7 @@ import type * as Span from "./pointers.Span.ts"; import type * as Spot from "./pointers.Spot.ts"; import type * as Stable from "./pointers.Stable.ts"; import type * as Swapped from "./pointers.Swapped.ts"; +import type * as TaggedVec from "./pointers.TaggedVec.ts"; import type * as To from "./pointers.To.ts"; import type * as Twin from "./pointers.Twin.ts"; import type * as Viewed from "./pointers.Viewed.ts"; @@ -2839,13 +2841,13 @@ export function rawPlaces(data: rs.ByteSlice, out: rs.ByteVec): void { })) { shelf = second; } - rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of({ v: value }.v & 0xff, shelf._0.value & 0xff)), undefined, true); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(value & 0xff, shelf._0.value & 0xff)), undefined, true); let shelf2: Shelf.Shelf = first; const value2: rs.u32 = shelf2._0.value; if (rs.usizeRem(data.length, 3) === 0) { shelf2 = second; } - rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of({ v: value2 }.v & 0xff, shelf2._0.value & 0xff)), undefined, true); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(value2 & 0xff, shelf2._0.value & 0xff)), undefined, true); second[Symbol.dispose](); first[Symbol.dispose](); flag[Symbol.dispose](); @@ -2984,7 +2986,8 @@ export function lent(n: rs.u8): rs.ByteVec { /** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ export function drained$Option_u16(v: rs.Vec, bits: rs.u8): rs.Vec { let next!: rs.u16 | undefined | rs.None; - const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const v2: rs.Vec = v; + const iter: Drained.Drained = { iter: rs.iterSlice(v2.items) as rs.Iter> }; const out: rs.Vec = new rs.Vec(); let i: rs.i32 = 0; while (true) { @@ -2997,6 +3000,9 @@ export function drained$Option_u16(v: rs.Vec, bits: rs.u8): out.items.push(next); i = rs.i32Add(i, 1); } else { + rs.vecSetLen(v2, 0, 4, (): rs.u16 | undefined => { + return (undefined as unknown) as rs.u16 | undefined; + }); return out; } } @@ -3005,7 +3011,8 @@ export function drained$Option_u16(v: rs.Vec, bits: rs.u8): /** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ export function drained$String(v: rs.Vec, bits: rs.u8): rs.Vec { let next!: rs.String | undefined; - const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const v2: rs.Vec = v; + const iter: Drained.Drained = { iter: rs.iterSlice(v2.items) as rs.Iter> }; const out: rs.Vec = new rs.Vec(); let i: rs.i32 = 0; while (true) { @@ -3018,6 +3025,9 @@ export function drained$String(v: rs.Vec, bits: rs.u8): rs.Vec { + return (undefined as unknown) as rs.String; + }); return out; } } @@ -3026,7 +3036,8 @@ export function drained$String(v: rs.Vec, bits: rs.u8): rs.Vec, bits: rs.u8): rs.Vec { let next!: rs.ByteVec | undefined; - const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const v2: rs.Vec = v; + const iter: Drained.Drained = { iter: rs.iterSlice(v2.items) as rs.Iter> }; const out: rs.Vec = new rs.Vec(); let i: rs.i32 = 0; while (true) { @@ -3039,6 +3050,9 @@ export function drained$Vec_u8(v: rs.Vec, bits: rs.u8): rs.Vec { + return (undefined as unknown) as rs.ByteVec; + }); return out; } } @@ -3047,7 +3061,8 @@ export function drained$Vec_u8(v: rs.Vec, bits: rs.u8): rs.Vec, bits: rs.u8): rs.Vec { let next!: rs.u32 | undefined; - const iter: Drained.Drained = { iter: rs.iterSlice(v.items) as rs.Iter> }; + const v2: rs.Vec = v; + const iter: Drained.Drained = { iter: rs.iterSlice(v2.items) as rs.Iter> }; const out: rs.Vec = new rs.Vec(); let i: rs.i32 = 0; while (true) { @@ -3060,11 +3075,39 @@ export function drained$u32(v: rs.Vec, bits: rs.u8): rs.Vec { out.items.push(next); i = rs.i32Add(i, 1); } else { + rs.vecSetLen(v2, 0, 4, (): rs.u32 => { + return (undefined as unknown) as rs.u32; + }); return out; } } } +/** + * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then + * drops or moves to the front. + */ +export function retained$Rc_pointers_Counted_pointers_behind_refs_closure10(v: rs.Vec>, keep: (a0: rs.Place>) => boolean): void { + let _10!: rs.usize; + const len: rs.usize = v.items.length; + const p: rs.Place> = rs.vecAsPtr(v, 4); + let write: rs.usize = 0; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = len; + while ((_10 = iterStart++) < iterEnd) { + const _15: rs.Place> = rs.ptrAdd(p, _10); + if (!keep(_15)) { + _15.v[Symbol.dispose](); + } else { + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); + write = rs.usizeAdd(write, 1); + } + } + rs.vecSetLen(v, write, 4, (): rs.Arc => { + return (undefined as unknown) as rs.Arc; + }); +} + /** * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then * drops or moves to the front. @@ -3079,11 +3122,13 @@ export function retained$String_pointers_behind_refs_closure6(v: rs.Vec = rs.ptrAdd(p, _10); if (keep(_15.v)) { - rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1, copy$String); write = rs.usizeAdd(write, 1); } } - rs.vecSplitOff(v, write); + rs.vecSetLen(v, write, 12, (): rs.String => { + return (undefined as unknown) as rs.String; + }); } /** @@ -3100,11 +3145,13 @@ export function retained$Vec_u8_pointers_behind_refs_closure8(v: rs.Vec = rs.ptrAdd(p, _10); if (keep(_15.v)) { - rs.ptrCopy(_15, rs.ptrAdd(p, write), 1); + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1, copy$Vec_u8); write = rs.usizeAdd(write, 1); } } - rs.vecSplitOff(v, write); + rs.vecSetLen(v, write, 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; + }); } /** @@ -3125,12 +3172,14 @@ export function retained$u32_pointers_behind_refs_closure4(v: rs.Vec, ke write = rs.usizeAdd(write, 1); } } - rs.vecSplitOff(v, write); + rs.vecSetLen(v, write, 4, (): rs.u32 => { + return (undefined as unknown) as rs.u32; + }); } /** The length of the `Vec` a pointer from a reference the body is given points at. */ export function vecLen(v: rs.ByteVec): rs.usize { - return { v }.v.length; + return v.length; } /** @@ -3164,23 +3213,23 @@ export function replaced(v: rs.ByteVec, w: rs.ByteVec, with_: rs.ByteVec, how: r const _7: rs.u8 = rs.u8Rem(how, 3); if (_7 === 0) { const pointer = p; - const old3 = rs.ptrRead(pointer); + const old3 = copy$Vec_u8(pointer.v); pointer.v = with_; old = old3; } else if (_7 === 1) { - const old2: rs.ByteVec = rs.ptrRead(p); + const old2: rs.ByteVec = copy$Vec_u8(p.v); p.v = with_; old = old2; } else { let slot: rs.ByteVec = (undefined as unknown) as rs.ByteVec; - rs.ptrCopy(p, rs.place(() => slot, (value) => slot = value), 1); + rs.ptrCopy(p, rs.place(() => slot, (value) => slot = value), 1, copy$Vec_u8); p.v = with_; old = slot; } const x = rs.placeIn(v); const y = rs.placeIn(w); - const old4 = rs.ptrRead(x); - x.v = rs.ptrRead(y); + const old4 = copy$Vec_u8(x.v); + x.v = copy$Vec_u8(y.v); y.v = old4; return old; } @@ -3229,8 +3278,8 @@ export function cellBehind(r: rs.Cell | undefined, how: rs.u8): rs.u32 { * from references to an `Arc`'s value, measured against each other. */ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { - let _112!: rs.usize; - let _152!: rs.u8; + let _133!: rs.usize; + let _173!: rs.u8; const bits: rs.u8 = option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0); const nums: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_2: rs.u8): rs.u32 => { return rs.u32Mul(_2 as rs.u32, 3); @@ -3264,9 +3313,9 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { })), undefined, true); } const nums2: rs.Vec = nums; - retained$u32_pointers_behind_refs_closure4(nums2, (n2: rs.Place): boolean => { - n2.v = rs.u32Add(n2.v, 1); - return rs.u32Rem(n2.v, 2) === 0; + retained$u32_pointers_behind_refs_closure4(nums2, (n3: rs.Place): boolean => { + n3.v = rs.u32Add(n3.v, 1); + return rs.u32Rem(n3.v, 2) === 0; }); rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(nums2.items), (_26: rs.u32): rs.u8 => { return _26 & 0xff; @@ -3285,17 +3334,30 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { return rs.u8Rem(r2.data[rs.idx(r2, 0)], 2) === 0; }); rs.iterExtendBytes(out, rs.iterFlatten(rs.iterSlice(runs2.items), (value: rs.ByteVec) => rs.iterSlice(rs.byteVecView(value)), true), "next"); - const nums3: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_27: rs.u8): rs.u32 => { - return _27 as rs.u32; + const dropped: rs.Cell = new rs.Cell(0); + const counted: rs.Vec> = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_27: rs.u8): rs.Arc => { + return new rs.Arc(new Counted.Counted(_27, dropped)); + }), undefined, 4, true); + retained$Rc_pointers_Counted_pointers_behind_refs_closure10(counted, (c: rs.Place>): boolean => { + return rs.u8Rem(c.v.value.id, 2) === 0; + }); + rs.byteVecPush(out, dropped.v); + rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(counted.items), (c2: rs.Arc): rs.u8 => { + return c2.value.id; + }), undefined, true); + rs.dropVec(counted); + rs.byteVecPush(out, dropped.v); + const nums3: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_28: rs.u8): rs.u32 => { + return _28 as rs.u32; }), undefined, 4, true); if (rs.sliceFrom(nums3.items).length >= 2) { const first: rs.Place = rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 0)); rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, rs.usizeSub(nums3.items.length, 1))), first) & 0xff); - const _109: rs.usize = nums3.items.length; + const _130: rs.usize = nums3.items.length; let iterStart: rs.usize = 0; - const iterEnd: rs.usize = _109; - while ((_112 = iterStart++) < iterEnd) { - rs.byteVecPush(out, rs.ptrAdd(first, _112).v & 0xff); + const iterEnd: rs.usize = _130; + while ((_133 = iterStart++) < iterEnd) { + rs.byteVecPush(out, rs.ptrAdd(first, _133).v & 0xff); } rs.byteVecPush(out, rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v & 0xff); rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v = 9; @@ -3303,42 +3365,200 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { } const v: rs.ByteVec = rs.byteSliceToByteVec(data); rs.byteVecPush(out, vecLen(v) & 0xff); - const _143: rs.ByteVec = rs.iterToByteVec(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), undefined, true); - refilled(v, _143, bits); + const _164: rs.ByteVec = rs.iterToByteVec(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), undefined, true); + refilled(v, _164, bits); rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); let iterStart2: rs.u8 = 0; const iterEnd2: rs.u8 = 3; - while ((_152 = iterStart2++) < iterEnd2) { - const how: rs.u8 = _152; + while ((_173 = iterStart2++) < iterEnd2) { + const how: rs.u8 = _173; const w2: rs.ByteVec = rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b6: rs.u8): rs.u8 => { return b6 ^ how; }), undefined, true); - const _164: rs.ByteVec = rs.byteVecOf(bits, rs.usizeAdd(how as rs.usize, 1)); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(replaced(v, w2, _164, how))), undefined, true); + const _185: rs.ByteVec = rs.byteVecOf(bits, rs.usizeAdd(how as rs.usize, 1)); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(replaced(v, w2, _185, how))), undefined, true); rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(w2)), undefined, true); rs.byteVecPush(out, 255); } const iter6: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _179: [rs.usize, rs.u8] | rs.Done = iter6.next(); - if (_179 === rs.DONE) { + const _200: [rs.usize, rs.u8] | rs.Done = iter6.next(); + if (_200 === rs.DONE) { break; } - const i2: rs.usize = _179[0]; - const b: rs.u8 = _179[1]; + const i2: rs.usize = _200[0]; + const b: rs.u8 = _200[1]; rs.byteVecPush(out, behind(b as rs.u32, i2 & 0xff) & 0xff); rs.byteVecPush(out, cellBehind(then$01qpu7s(rs.u8Rem(b, 3) !== 0, (): rs.Cell => { return new rs.Cell(b as rs.u32); }), b) & 0xff); } const arc: rs.Arc = new rs.Arc(bits as rs.u32); + const p: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); const q: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); - rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arcRaw(arc, rs.arcPlace(arc), "Arc"), q) & 0xff); + rs.byteVecPush(out, rs.ptrOffsetFrom(p, q) & 0xff); rs.byteVecPush(out, q.v & 0xff); + rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arcRaw(arc, rs.arcPlace(arc), "Arc"), p) & 0xff); + rs.byteVecPush(out, from$convert_From_ref_pointers_Halves32_ptr_NonNull_u32({ first: bits as rs.u32, second: ((bits as rs.u32) ^ 85) >>> 0 }).v & 0xff); arc[Symbol.dispose](); } +export function from$convert_From_ref_pointers_Halves32_ptr_NonNull_u32(halves: Halves32.Halves32): rs.Place { + return rs.place(() => halves.second, (value) => halves.second = value); +} + +/** + * `with` written whole into the `Vec` `r` is, in place, dropping nothing, as a body that + * can't see where it is kept writes it. + */ +export function refillIn(r: rs.ByteVec, with_: rs.ByteVec): void { + rs.placeIn(r).v = with_; +} + +/** + * What the `Vec` `v` held, moved out through a pointer made each way `how` picks: from a + * shared reborrow, cast to one to a `MaybeUninit` or a `ManuallyDrop` of it, or swapped + * through such a cast with another; then `with` is written whole into `v` in place. + */ +export function movedVec(v: rs.ByteVec, with_: rs.ByteVec, how: rs.u8): rs.ByteVec { + let old!: rs.ByteVec; + const _5: rs.u8 = rs.u8Rem(how, 4); + if (_5 === 0) { + old = copy$Vec_u8(v); + } else if (_5 === 1) { + old = copy$mem_MaybeUninit_Vec_u8(rs.placeIn(v).v); + } else if (_5 === 2) { + old = copy$Vec_u8(rs.placeIn(v).v); + } else { + let other: rs.ByteVec = rs.byteVecFrom(Uint8Array.of(how)); + const x = rs.placeIn(v); + const y = rs.place(() => other, (value) => other = value); + const old2 = copy$mem_MaybeUninit_Vec_u8(x.v); + x.v = copy$mem_MaybeUninit_Vec_u8(y.v); + y.v = old2; + old = other; + } + refillIn(v, with_); + return old; +} + +/** + * What an `Option`, a tuple, and a struct each holding a `Vec` held, moved out through + * pointers from `&mut`s to them: read, or copied, replaced, and swapped, as `how` picks; + * then `with` is written whole into each `Vec` they hold, in place. + */ +export function movedNested(o: rs.Place, t: [rs.ByteVec, rs.u8], s: TaggedVec.TaggedVec, with_: rs.ByteSlice, how: rs.u8): [rs.ByteVec | undefined, [rs.ByteVec, rs.u8], TaggedVec.TaggedVec] { + let old!: [rs.ByteVec | undefined, [rs.ByteVec, rs.u8], TaggedVec.TaggedVec]; + if (rs.u8Rem(how, 2) === 0) { + old = [copy$Option_Vec_u8(o.v), copy$tuple2_Vec_u8_u8(t), copy$pointers_TaggedVec(s)] as [rs.ByteVec | undefined, [rs.ByteVec, rs.u8], TaggedVec.TaggedVec]; + } else { + let slot: rs.ByteVec | undefined = (undefined as unknown) as rs.ByteVec | undefined; + rs.ptrCopy(o, rs.place(() => slot, (value) => slot = value), 1, copy$Option_Vec_u8); + const _22: [rs.ByteVec, rs.u8] = t; + const _23: [rs.ByteVec, rs.u8] = [rs.byteVecFrom(Uint8Array.of(how)), how]; + const pointer = _22; + const old2 = copy$tuple2_Vec_u8_u8(pointer); + rs.ptrWrite(pointer, _23); + const oldT: [rs.ByteVec, rs.u8] = old2; + const oldS: TaggedVec.TaggedVec = { v: rs.byteVecFrom(Uint8Array.of(how)), tag: how }; + const x = s; + const y = oldS; + const old3 = copy$pointers_TaggedVec(x); + rs.ptrWrite(x, copy$pointers_TaggedVec(y)); + rs.ptrWrite(y, old3); + old = [slot, oldT, oldS] as [rs.ByteVec | undefined, [rs.ByteVec, rs.u8], TaggedVec.TaggedVec]; + } + if (o.v !== undefined) { + const _39: rs.ByteVec = rs.byteSliceToByteVec(with_); + refillIn(o.v, _39); + } + const _42: rs.ByteVec = rs.byteSliceToByteVec(with_); + refillIn(t[0], _42); + const _45: rs.ByteVec = rs.byteSliceToByteVec(with_); + refillIn(s.v, _45); + return old; +} + +/** + * The `Option` of a cell a shared reference points at, moved out through a pointer from + * it, whose cell is then set. + */ +export function cellMoved(r: rs.Cell | undefined): rs.Cell | undefined { + const old: rs.Cell | undefined = copy$Option_Cell_u32(r); + if (old !== undefined) { + rs.cellSet(old, rs.u32Add(old.v, 5)); + return old; + } + return old; +} + +/** + * Values holding a library object, moved out through pointers made each way, at any + * depth, and then written in place: what was moved out keeps what it held. + */ +export function movedOut(data: rs.ByteSlice, out: rs.ByteVec): void { + let _7!: rs.u8; + let _28!: rs.u8; + const with_: (a0: rs.u8) => rs.ByteVec = (n: rs.u8): rs.ByteVec => { + return rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b2: rs.u8): rs.u8 => { + return rs.u8WrappingAdd(b2, n); + }), undefined, true); + }; + let iterStart: rs.u8 = 0; + const iterEnd: rs.u8 = 4; + while ((_7 = iterStart++) < iterEnd) { + const how: rs.u8 = _7; + const v: rs.ByteVec = rs.byteSliceToByteVec(data); + const _14: rs.ByteVec = with_(rs.u8Add(how, 1)); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(movedVec(v, _14, how))), undefined, true); + rs.byteVecPush(out, 254); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); + rs.byteVecPush(out, 255); + } + let iterStart2: rs.u8 = 0; + const iterEnd2: rs.u8 = 2; + while ((_28 = iterStart2++) < iterEnd2) { + const how2: rs.u8 = _28; + let o: rs.ByteVec | undefined = option_map$1px2rk4(rs.byteSliceFirst(data), (_2: rs.u8): rs.ByteVec => { + return rs.byteSliceToByteVec(data); + }); + const t: [rs.ByteVec, rs.u8] = [rs.byteSliceToByteVec(data), how2]; + const s: TaggedVec.TaggedVec = { v: rs.byteSliceToByteVec(data), tag: how2 }; + const _46: rs.ByteSlice = rs.byteVecSlice(with_(9)); + const _42: [rs.ByteVec | undefined, [rs.ByteVec, rs.u8], TaggedVec.TaggedVec] = movedNested(rs.place(() => o, (value) => o = value), t, s, _46, how2); + const oldO: rs.ByteVec | undefined = _42[0]; + const oldT: [rs.ByteVec, rs.u8] = _42[1]; + const oldS: TaggedVec.TaggedVec = _42[2]; + const iter3: rs.Iter = rs.iterFlatten(rs.iterSlice([oldO, oldT[0], oldS.v, o, t[0], s.v]), (value: rs.ByteVec | undefined) => rs.iterOption(value), true, undefined, true); + while (true) { + const _68: rs.ByteVec | rs.Done = iter3.next(); + if (_68 === rs.DONE) { + break; + } + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(_68)), undefined, true); + rs.byteVecPush(out, 254); + } + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(oldT[1], oldS.tag, t[1], s.tag)), undefined, true); + } + for (const b of rs.byteSliceView(data)) { + const cell: rs.Cell | undefined = then$01qpu7s(rs.u8Rem(b, 3) !== 0, (): rs.Cell => { + return new rs.Cell(b as rs.u32); + }); + const old2: rs.Cell | undefined = cellMoved(cell); + const iter5: rs.Iter | undefined> = rs.iterSlice([cell, old2]); + while (true) { + const _99: rs.Cell | undefined | rs.Done = iter5.next(); + if (_99 === rs.DONE) { + break; + } + rs.byteVecPush(out, option_mapOr$18adzvj(_99, 0, (a0: rs.Cell): rs.u32 => { + return a0.v; + }) & 0xff); + } + } +} + /** * Pointers to slices cast to ones to arrays of their elements, of bytes and of numbers: * read whole, borrowed, and written through, at every offset; compared with the arrays diff --git a/ts/fixtures/lang/pointers/src/lib.rs b/ts/fixtures/lang/pointers/src/lib.rs index 793f2957c..0da7b29d8 100644 --- a/ts/fixtures/lang/pointers/src/lib.rs +++ b/ts/fixtures/lang/pointers/src/lib.rs @@ -54,5 +54,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("pointers::generic_unions", |_, data, out| { pointers::generic_unions(data, out) }), + ("pointers::moved_out", |_, data, out| pointers::moved_out(data, out)), ] } diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index a4817f92c..f3dbee1c6 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2446,6 +2446,9 @@ fn drained(v: Vec, bits: u8) -> Vec { out.push(next); i += 1; } + // Its elements moved out, its room is freed without dropping them again. + let mut v = mem::ManuallyDrop::into_inner(v); + unsafe { v.set_len(0) }; out } @@ -2464,8 +2467,8 @@ fn retained(v: &mut Vec, mut keep: impl FnMut(&mut T) -> bool) { write += 1; } } - // What `set_len` leaves past the length, which nothing drops again. - mem::forget(v.split_off(write)); + // What is past the length, dropped or moved, which nothing drops again. + unsafe { v.set_len(write) }; } /// The length of the `Vec` a pointer from a reference the body is given points at. @@ -2576,6 +2579,17 @@ pub fn behind_refs(data: &[u8], out: &mut Vec) { r[0] % 2 == 0 }); out.extend(runs.iter().flatten()); + // Each element it drops, dropped once. + let dropped = Cell::new(0); + let mut counted: Vec> = data + .iter() + .map(|&id| Rc::new(Counted { id, dropped: &dropped })) + .collect(); + retained(&mut counted, |c| c.id % 2 == 0); + out.push(dropped.get()); + out.extend(counted.iter().map(|c| c.id)); + drop(counted); + out.push(dropped.get()); // From a reference the body borrowed, where the element is. let mut nums: Vec = data.iter().map(|&b| u32::from(b)).collect(); @@ -2616,6 +2630,143 @@ pub fn behind_refs(data: &[u8], out: &mut Vec) { let q = ptr::from_ref(&*arc); out.push(unsafe { p.offset_from(q) } as u8); out.push(unsafe { *q } as u8); + let n: NonNull = (&*arc).into(); + out.push(unsafe { n.as_ptr().offset_from(p) } as u8); + // A conversion of the crate's own, which runs as written. + let halves = Halves32 { + first: u32::from(bits), + second: u32::from(bits) ^ 0x55, + }; + let second: NonNull = (&halves).into(); + out.push(unsafe { *second.as_ptr() } as u8); +} + +/// Two numbers, the second of which a pointer from a reference to them points at. +struct Halves32 { + first: u32, + second: u32, +} + +impl From<&Halves32> for NonNull { + fn from(halves: &Halves32) -> Self { + let _ = halves.first; + NonNull::from(&halves.second) + } +} + +/// `with` written whole into the `Vec` `r` is, in place, dropping nothing, as a body that +/// can't see where it is kept writes it. +#[inline(never)] +fn refill_in(r: &mut Vec, with: Vec) { + unsafe { ptr::write(r, with) }; +} + +/// What the `Vec` `v` held, moved out through a pointer made each way `how` picks: from a +/// shared reborrow, cast to one to a `MaybeUninit` or a `ManuallyDrop` of it, or swapped +/// through such a cast with another; then `with` is written whole into `v` in place. +fn moved_vec(v: &mut Vec, with: Vec, how: u8) -> Vec { + let old = match how % 4 { + 0 => unsafe { ptr::read(&*v as *const Vec) }, + 1 => unsafe { ptr::read(ptr::from_mut(v).cast::>>()).assume_init() }, + 2 => mem::ManuallyDrop::into_inner(unsafe { ptr::read(ptr::from_mut(v).cast::>>()) }), + _ => { + let mut other = MaybeUninit::new(vec![how]); + unsafe { ptr::swap(ptr::from_mut(v).cast::>>(), &mut other) }; + unsafe { other.assume_init() } + } + }; + refill_in(v, with); + old +} + +/// A `Vec` with a tag, which a pointer to one moves out whole. +struct TaggedVec { + v: Vec, + tag: u8, +} + +/// What an `Option`, a tuple, and a struct each holding a `Vec` held, moved out through +/// pointers from `&mut`s to them: read, or copied, replaced, and swapped, as `how` picks; +/// then `with` is written whole into each `Vec` they hold, in place. +fn moved_nested( + o: &mut Option>, + t: &mut (Vec, u8), + s: &mut TaggedVec, + with: &[u8], + how: u8, +) -> (Option>, (Vec, u8), TaggedVec) { + let old = match how % 2 { + 0 => unsafe { (ptr::read(o), ptr::read(t), ptr::read(s)) }, + _ => unsafe { + let mut slot = MaybeUninit::uninit(); + ptr::copy_nonoverlapping(ptr::from_mut(o), slot.as_mut_ptr(), 1); + let old_t = ptr::replace(ptr::from_mut(t), (vec![how], how)); + let mut old_s = TaggedVec { v: vec![how], tag: how }; + ptr::swap(ptr::from_mut(s), &mut old_s); + (slot.assume_init(), old_t, old_s) + }, + }; + if let Some(v) = o { + refill_in(v, with.to_vec()); + } + refill_in(&mut t.0, with.to_vec()); + refill_in(&mut s.v, with.to_vec()); + old +} + +/// The `Option` of a cell a shared reference points at, moved out through a pointer from +/// it, whose cell is then set. +fn cell_moved(r: &Option>) -> Option> { + let old = unsafe { ptr::read(r) }; + if let Some(cell) = &old { + cell.set(cell.get() + 5); + } + old +} + +/// Values holding a library object, moved out through pointers made each way, at any +/// depth, and then written in place: what was moved out keeps what it held. +pub fn moved_out(data: &[u8], out: &mut Vec) { + let with = |n: u8| -> Vec { data.iter().map(|b| b.wrapping_add(n)).collect() }; + for how in 0..4 { + let mut v = data.to_vec(); + let old = moved_vec(&mut v, with(how + 1), how); + out.extend(&old); + out.push(0xfe); + out.extend(&v); + out.push(0xff); + } + for how in 0..2 { + let mut o = data.first().map(|_| data.to_vec()); + let mut t = (data.to_vec(), how); + let mut s = TaggedVec { + v: data.to_vec(), + tag: how, + }; + let (old_o, old_t, old_s) = moved_nested(&mut o, &mut t, &mut s, &with(9), how); + for v in [ + old_o.as_ref(), + Some(&old_t.0), + Some(&old_s.v), + o.as_ref(), + Some(&t.0), + Some(&s.v), + ] + .into_iter() + .flatten() + { + out.extend(v); + out.push(0xfe); + } + out.extend([old_t.1, old_s.tag, t.1, s.tag]); + } + for &b in data { + let cell = (b % 3 != 0).then(|| Cell::new(u32::from(b))); + let old = cell_moved(&cell); + for c in [&cell, &old] { + out.push(c.as_ref().map_or(0, Cell::get) as u8); + } + } } /// Pointers to slices cast to ones to arrays of their elements, of bytes and of numbers: diff --git a/ts/runtime/rs.ts b/ts/runtime/rs.ts index 658e1068d..f75430d95 100644 --- a/ts/runtime/rs.ts +++ b/ts/runtime/rs.ts @@ -143,40 +143,11 @@ export function place(get: () => T, set: (value: T) => void): Place { } /** - * A place that is the object `target`, which a write of a whole value lands in, as - * `replaceIn` writes: a raw pointer from a `&mut` to an object, which others may hold. + * The raw pointer from a `&mut` to `target`, an object others may hold, which a write of a + * whole value lands in, as `replaceIn` writes. */ -class WrittenIn implements Place { - readonly target: T; - /** The levels of arrays whose items a copy of `target` copies too, as `objectCopy`'s. */ - readonly depth: number; - - constructor(target: T, depth: number) { - this.target = target; - this.depth = depth; - } - - get v(): T { - return this.target; - } - - set v(value: T) { - writeIn(this.target, value); - } -} - -/** The raw pointer from a `&mut` to `target`, an object, as `WrittenIn` is. */ -export function placeIn(target: T, depth = 0): Place { - return new WrittenIn(target, depth); -} - -/** - * What a read through `p` moves out, as `ptr::read` does: what it holds, or a copy of the - * object a `placeIn` is, which a write through it would change. - */ -export function ptrRead(p: Place): T { - const home = ptrHome(p); - return home instanceof WrittenIn ? (objectCopy(home.target, home.depth) as T) : p.v; +export function placeIn(target: T): Place { + return place(() => target, (value) => writeIn(target, value)); } // Raw pointers @@ -483,9 +454,6 @@ export function ptrCopy(src: Place, dst: Place, n: number, copy?: (valu return; } const values = Array.from({ length: n }, (_, i) => elemGet(from.array, from.index + i)); - // What a `placeIn` holds changes with the next write through it. - const home = ptrHome(from); - if (home instanceof WrittenIn) values[0] = objectCopy(home.target, home.depth) as T; // Memory nothing wrote yet is copied as it is, which no value's copy can read. for (let i = 0; i < n; i++) elemSet(to.array, to.index + i, copy && values[i] !== undefined ? copy(values[i]) : values[i]); } diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 2bd85b9a3..48ad20b8c 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -1,10 +1,13 @@ -//! Copies of `Copy` aggregates, by their type. +//! Copies of `Copy` aggregates, and of values a read through a pointer moves out, by +//! their type. //! //! JS shares an object where Rust copies its value, so a `Copy` value that is a JS object //! the crate's code writes into in place, or compares pointers to, is copied where Rust -//! copies it. A function made for its type copies what it holds by value and shares what -//! it holds by reference, as Rust does. A value of a type nothing writes into or compares -//! pointers to never changes once made, so sharing it is a copy no one can tell apart. +//! copies it, as is each such object a read through a pointer moves out, a library +//! object such as a `Vec` included, one object deep. A function made for its type copies +//! what it holds by value and shares what it holds by reference, as Rust does. A value of +//! a type nothing writes into or compares pointers to never changes once made, so sharing +//! it is a copy no one can tell apart. use std::cell::RefCell; @@ -56,6 +59,14 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { // Records of functions and pointers, which JS shares. Some(Std::WakerVTable | Std::RawWaker) => (true, Vec::new()), Some(Std::MaybeUninit | Std::ManuallyDrop) => return self.changes(args.type_at(0)), + // A library object a whole write lands in in place, which a copy copies one + // object deep; through a shared reference too, where it has interior + // mutability, which no write the crate shows records. + Some(_) if self.write_in(ty).is_some() => { + return self.writes.contains(&self.writes.into, false, self.tcx, ty) + || self.addressed(ty) + || !ty.is_freeze(self.tcx, self.typing_env()); + } Some(_) => return false, None if self.own(adt.did()) && adt.all_fields().next().is_some() => { (true, adt.all_fields().map(|f| self.field_ty(f.did, args)).collect()) @@ -317,6 +328,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { _ => self.copy_result(value, residual, span), }) } + // A library object, which [`Self::changes`] says a whole write lands in. + Some(_) => ret(Expr::rs("objectCopy", vec![v()])), _ => { let info = self.adt(*adt, span); match info.repr { diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index d749adccc..fdc95fd7e 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -526,16 +526,16 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let done = |e: Expr, effect: bool| Some(Some((e, effect))); match (owner.as_str(), name.as_str()) { // A pointer from a reference to an `Arc`'s value, which is that value's one place. - ("ptr", "from_ref") | ("From", "from") - if (owner == "ptr" - || crate::lower::pointer::raw_pointee(tcx, dest.ty(&self.mir.local_decls, tcx).ty).is_some()) + ("ptr", "from_ref") | ("From", "from") | ("Into", "into") + if crate::lower::pointer::from_reference(tcx, def_id, gargs) && let Some(p) = self.arc_raw(&args[0].node, span, out) => { done(p, false) } // A pointer from a reference to a value JS keeps in a place, as `&raw const *r` makes. ("ptr", "from_ref" | "from_mut") | ("From", "from") | ("Into", "into") - if let Some(r) = args[0].node.place() + if crate::lower::pointer::from_reference(tcx, def_id, gargs) + && let Some(r) = args[0].node.place() && crate::lower::pointer::raw_pointee(tcx, dest.ty(&self.mir.local_decls, tcx).ty) .is_some_and(|pointee| self.behind_place(r, pointee)) => { @@ -1834,7 +1834,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { /// How many levels of arrays of objects a write may land in `ty` nests, whose items a /// copy of a view of one copies too, unlike references, which move as they are: the /// runtime's `depth`, if any. - pub(super) fn copy_depth(&self, ty: Ty<'tcx>) -> Option { + fn copy_depth(&self, ty: Ty<'tcx>) -> Option { let mut depth = 0; let mut at = ty; while let ty::Array(elem, _) = at.kind() diff --git a/ts/src/lower/mir/mod.rs b/ts/src/lower/mir/mod.rs index 32a536015..e1f6b6096 100644 --- a/ts/src/lower/mir/mod.rs +++ b/ts/src/lower/mir/mod.rs @@ -3634,6 +3634,14 @@ fn subst(e: &mut Expr, name: &str, value: &Expr) -> bool { { Some(payload.clone()) } + // A pointer made here of a shared reference holds its value. + (Expr::Object(members), ts::Key::Name(key)) + if key == "v" + && let [(ts::Key::Name(v), value)] = members.as_slice() + && v == "v" => + { + Some(value.clone()) + } (_, ts::Key::Name(key)) if key == "v" => step::peel(base).place_read().cloned(), _ => None, }; @@ -3685,16 +3693,6 @@ fn subst(e: &mut Expr, name: &str, value: &Expr) -> bool { { *e = value.clone(); } - // A read through a pointer made here of a shared reference is the value it holds. - if let Expr::Call(f, args) = &*e - && let (Expr::Rs(read), [p]) = (&**f, args.as_slice()) - && read == "ptrRead" - && let Expr::Object(members) = step::peel(p) - && let [(ts::Key::Name(v), value)] = members.as_slice() - && v == "v" - { - *e = value.clone(); - } // The elements of a slice of all of an array are the array, and a copy of them its copy. if let Expr::Call(f, args) = &*e && let (Expr::Rs(read), [slice]) = (&**f, args.as_slice()) diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 6a21391ae..94a781cb3 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -209,10 +209,12 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { _ => None, }; } - let (def_id, _, args) = self.def_call(local)?; + let (def_id, gargs, args) = self.def_call(local)?; let (owner, name) = library::key(self.tcx, def_id)?; match (owner.as_str(), name.as_str()) { - ("ptr", "from_ref" | "from_mut") | ("From", "from") | ("Into", "into") => { + ("ptr", "from_ref" | "from_mut") | ("From", "from") | ("Into", "into") + if crate::lower::pointer::from_reference(self.tcx, def_id, gargs) => + { let block = self.def_block(local)?; let at = Location { block, diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index a97d2ac79..9087d0788 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -281,7 +281,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { /// `r` borrowed, as [`Self::borrowed_pointer`] finds it. Otherwise it is a place of its /// own, as `ptr::from_ref` makes: of the value, where `r` is shared and nothing may write /// a whole value through the pointer, and else an `rs.placeIn` writing a whole value into - /// the object `r` is in place, as `*r = v` does, which a read moving out copies. + /// the object `r` is in place, as `*r = v` does. pub(super) fn behind_reference(&mut self, r: Place<'tcx>, span: Span, out: &mut Vec) -> Expr { let tcx = self.tcx; if let Some(p) = self.borrowed_pointer(r, span, out) { @@ -309,9 +309,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { format!("a raw pointer to `{ty}` behind a reference, which JS can't write a whole value into in place"), ); } - let mut made = vec![value]; - made.extend(self.copy_depth(ty)); - Expr::rs("placeIn", made) + Expr::rs("placeIn", vec![value]) } /// The raw pointer to the place the body shows the reference `r` borrowed, if it does: diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 63ae9914c..3c505b98a 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -292,7 +292,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// Whether std's `def_id` with `args` makes a raw pointer to what its first argument, a /// reference, points at: `UnsafeCell::get`, `MaybeUninit::as_ptr` and `as_mut_ptr`, -/// `ptr::from_ref` and `from_mut`, and `NonNull::from` and `into`. +/// `ptr::from_ref` and `from_mut`, and `NonNull::from` and `into` of a reference to its +/// pointee, which only std's impl converts, as any other would overlap it. pub fn from_reference<'tcx>( tcx: ty::TyCtxt<'tcx>, def_id: rustc_hir::def_id::DefId, @@ -311,7 +312,8 @@ pub fn from_reference<'tcx>( "From" => (args.type_at(0), args.type_at(1)), _ => (args.type_at(1), args.type_at(0)), }; - matches!(to.kind(), ty::Adt(adt, _) if Std::of(tcx, adt.did()) == Some(Std::NonNull)) && from.is_ref() + matches!(to.kind(), ty::Adt(adt, to) if Std::of(tcx, adt.did()) == Some(Std::NonNull) + && from.ref_mutability().is_some() && from.builtin_deref(true) == Some(to.type_at(0))) } _ => false, } @@ -1138,8 +1140,6 @@ fn read<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, ty: Ty<'tcx>, span: Span) -> { value.clone() } - // It may be an `rs.placeIn`, whose object the next write through it changes. - _ if b.t.write_in(ty).is_some() => Expr::rs("ptrRead", vec![p]), _ => p.member("v"), }, Pointee::Object => p, From 925f1bc8f303c24ee6de983e76484f8ab67bfb27 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 07:12:31 -0800 Subject: [PATCH 16/29] fix(ts): copy what a cell or lock holds inline when moving it out A move-out read of a RefCell, Mutex, Cell, or OnceCell copied the object one deep but shared the value it holds inline, so setting a cell inside the moved-out RefCell changed the original's. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 2 +- ts/fixtures/lang/pointers/expected/lib.ts | 12 +++++ .../lang/pointers/expected/pointers.ts | 48 ++++++++++++++++++- ts/fixtures/lang/pointers/src/pointers.rs | 27 ++++++++++- ts/src/lower/copy.rs | 36 ++++++++++++-- 5 files changed, 119 insertions(+), 6 deletions(-) diff --git a/ts/README.md b/ts/README.md index b5b1c65b4..f36fdffed 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: 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` | `rs.Cow`: 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`, `Reverse`, `NonZero`, `AssertUnwindSafe` | 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`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | 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` 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `Vec` one object deep, and one with interior mutability, such as a `Cell`, always | +| `*const T`, `*mut T`, `NonNull` | 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` 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `Vec` one object deep, with what a cell or lock holds inline copied too, and one with interior mutability, such as a `Cell`, always | | `MaybeUninit` | `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`, 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 | diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index 16f3e9201..70d8923c9 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -73,6 +73,18 @@ export function copy$array_u8(v: Uint8Array): Uint8Array { return v.slice(); } +export function copy$cell_OnceCell_Cell_u32(v: rs.OnceCell>): rs.OnceCell> { + const c = rs.objectCopy(v); + c.v = copy$Option_Cell_u32(c.v); + return c; +} + +export function copy$cell_RefCell_Cell_u32(v: rs.RefCell>): rs.RefCell> { + const c = rs.objectCopy(v); + c.value = copy$Cell_u32(c.value); + return c; +} + export function copy$mem_MaybeUninit_Vec_u8(v: rs.ByteVec): rs.ByteVec { if (v === undefined) { return v; diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 50cc2073d..d7dc8220e 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; +import { copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$cell_OnceCell_Cell_u32, copy$cell_RefCell_Cell_u32, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; import { thenSome$1gvhopg, then$01qpu7s, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, map$1px2rk4 as option_map$1px2rk4, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$18adzvj as option_mapOr$18adzvj, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; import type * as Aimed from "./pointers.Aimed.ts"; @@ -3493,6 +3493,23 @@ export function cellMoved(r: rs.Cell | undefined): rs.Cell | und return old; } +/** + * The `RefCell` and the `OnceCell` of a cell that shared references point at, moved out + * through pointers from them, whose cells, held inline, are then set. + */ +export function inlineMoved(r: rs.RefCell>, o: rs.OnceCell>): [rs.RefCell>, rs.OnceCell>] { + const old: [rs.RefCell>, rs.OnceCell>] = [copy$cell_RefCell_Cell_u32(rs.placeIn(r).v), copy$cell_OnceCell_Cell_u32(rs.placeIn(o).v)]; + const cell: rs.RefCell> = rs.refCellBorrow(old[0]); + rs.cellSet(cell.value, rs.u32Add(cell.value.v, 9)); + rs.refRelease(cell); + const _19: rs.Cell | undefined = old[1].v; + if (_19 !== undefined) { + rs.cellSet(_19, rs.u32Add(_19.v, 9)); + return old; + } + return old; +} + /** * Values holding a library object, moved out through pointers made each way, at any * depth, and then written in place: what was moved out keeps what it held. @@ -3556,6 +3573,35 @@ export function movedOut(data: rs.ByteSlice, out: rs.ByteVec): void { return a0.v; }) & 0xff); } + const r: rs.RefCell> = new rs.RefCell(new rs.Cell(b as rs.u32)); + const o2: rs.OnceCell> = new rs.OnceCell(undefined); + if (rs.u8Rem(b, 2) === 0) { + const _116: rs.Cell = new rs.Cell(rs.u32Add(b as rs.u32, 1)); + rs.onceSet(o2, _116); + } + const _122: [rs.RefCell>, rs.OnceCell>] = inlineMoved(r, o2); + const oldR: rs.RefCell> = _122[0]; + const oldO2: rs.OnceCell> = _122[1]; + const iter6: rs.Iter>> = rs.iterSlice([r, oldR]); + while (true) { + const _130: rs.RefCell> | rs.Done = iter6.next(); + if (_130 === rs.DONE) { + break; + } + const _139: rs.RefCell> = rs.refCellBorrow(_130); + rs.byteVecPush(out, _139.value.v & 0xff); + rs.refRelease(_139); + } + const iter7: rs.Iter>> = rs.iterSlice([o2, oldO2]); + while (true) { + const _145: rs.OnceCell> | rs.Done = iter7.next(); + if (_145 === rs.DONE) { + break; + } + rs.byteVecPush(out, option_mapOr$18adzvj(_145.v, 0, (a02: rs.Cell): rs.u32 => { + return a02.v; + }) & 0xff); + } } } diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index f3dbee1c6..e0b95024e 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -11,7 +11,7 @@ //! raw pointers of an `Arc` and an `Rc`, counted and taken back; and unions over a type //! parameter, each instance laid out at its own offsets. -use std::cell::{Cell, UnsafeCell}; +use std::cell::{Cell, OnceCell, RefCell, UnsafeCell}; use std::mem::{self, MaybeUninit}; use std::ptr::{self, NonNull}; use std::rc::Rc; @@ -2724,6 +2724,19 @@ fn cell_moved(r: &Option>) -> Option> { old } +/// The `RefCell` and the `OnceCell` of a cell that shared references point at, moved out +/// through pointers from them, whose cells, held inline, are then set. +fn inline_moved(r: &RefCell>, o: &OnceCell>) -> (RefCell>, OnceCell>) { + let old = unsafe { (ptr::read(r), ptr::read(o)) }; + let cell = old.0.borrow(); + cell.set(cell.get() + 9); + drop(cell); + if let Some(cell) = old.1.get() { + cell.set(cell.get() + 9); + } + old +} + /// Values holding a library object, moved out through pointers made each way, at any /// depth, and then written in place: what was moved out keeps what it held. pub fn moved_out(data: &[u8], out: &mut Vec) { @@ -2766,6 +2779,18 @@ pub fn moved_out(data: &[u8], out: &mut Vec) { for c in [&cell, &old] { out.push(c.as_ref().map_or(0, Cell::get) as u8); } + let r = RefCell::new(Cell::new(u32::from(b))); + let o = OnceCell::new(); + if b % 2 == 0 { + let _ = o.set(Cell::new(u32::from(b) + 1)); + } + let (old_r, old_o) = inline_moved(&r, &o); + for r in [&r, &old_r] { + out.push(r.borrow().get() as u8); + } + for o in [&o, &old_o] { + out.push(o.get().map_or(0, Cell::get) as u8); + } } } diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 48ad20b8c..5fcc66d66 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -65,7 +65,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some(_) if self.write_in(ty).is_some() => { return self.writes.contains(&self.writes.into, false, self.tcx, ty) || self.addressed(ty) - || !ty.is_freeze(self.tcx, self.typing_env()); + || !ty.is_freeze(self.tcx, self.typing_env()) + || self.inline_held(ty).is_some_and(|(_, held)| self.changes(held)); } Some(_) => return false, None if self.own(adt.did()) && adt.all_fields().next().is_some() => { @@ -79,6 +80,21 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { || parts.into_iter().any(|t| self.changes(t)) } + /// The field of a library object `ty` that holds its value inline rather than on the + /// heap, which a copy of the object one object deep shares, and the type held there: a + /// cell's or a lock's value, or a one-time cell's, which holds nothing until set. + fn inline_held(&self, ty: Ty<'tcx>) -> Option<(&'static str, Ty<'tcx>)> { + let ty::Adt(adt, args) = ty.kind() else { + return None; + }; + match Std::of(self.tcx, adt.did())? { + Std::Cell => Some(("v", args.type_at(0))), + Std::RefCell | Std::Mutex => Some(("value", args.type_at(0))), + Std::OnceCell | Std::OnceLock => Some(("v", Ty::new_option(self.tcx, args.type_at(0)))), + _ => None, + } + } + /// Whether the runs before compared pointers to a `ty`, or to a type it holds by value, /// so each copy of one is at an address of its own, as in Rust, wherever it is read. pub fn held_addressed(&self, ty: Ty<'tcx>) -> bool { @@ -328,8 +344,22 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { _ => self.copy_result(value, residual, span), }) } - // A library object, which [`Self::changes`] says a whole write lands in. - Some(_) => ret(Expr::rs("objectCopy", vec![v()])), + // A library object, which [`Self::changes`] says a whole write lands in, and a + // copy of what it holds inline. + Some(_) => { + let copy = Expr::rs("objectCopy", vec![v()]); + let held = self.inline_held(ty).filter(|&(_, held)| self.changes(held)); + let Some((field, held)) = held else { + return ret(copy); + }; + let c = || Expr::ident("c"); + let read = self.snapshot(c().member(field), held, span); + vec![ + StmtKind::decl(false, "c", None, Some(copy)).at(None), + StmtKind::Expr(c().member(field).assign(read)).at(None), + StmtKind::Return(Some(c())).at(None), + ] + } _ => { let info = self.adt(*adt, span); match info.repr { From d4531da157275f6e4055664ec3d42335cece9dce Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 07:36:28 -0800 Subject: [PATCH 17/29] fix(ts): copy a range's ends and a borrowed cell's value when moving them out A Range of cells shared its ends with the original, and a Cell that Cell::from_mut made of a place copied its accessors. Each library object now lists what it holds inline; a cell is rebuilt from its value, and one whose inline state JS can't reach is refused where a copy would need it. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 2 +- ts/fixtures/lang/pointers/expected/lib.ts | 9 +- .../lang/pointers/expected/pointers.ts | 29 ++++++- ts/fixtures/lang/pointers/src/pointers.rs | 18 ++++ ts/src/lower/copy.rs | 85 ++++++++++++++----- 5 files changed, 118 insertions(+), 25 deletions(-) diff --git a/ts/README.md b/ts/README.md index f36fdffed..4a6b1a73d 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: 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` | `rs.Cow`: 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`, `Reverse`, `NonZero`, `AssertUnwindSafe` | 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`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | 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` 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `Vec` one object deep, with what a cell or lock holds inline copied too, and one with interior mutability, such as a `Cell`, always | +| `*const T`, `*mut T`, `NonNull` | 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` 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `Vec` one object deep, with what a cell, lock, or range holds inline copied too, refused where JS keeps it out of reach, and one with interior mutability, such as a `Cell`, always | | `MaybeUninit` | `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`, 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 | diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index 70d8923c9..7d92b735d 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -22,7 +22,7 @@ import type * as TaggedVec from "./pointers.TaggedVec.ts"; import type * as Twin from "./pointers.Twin.ts"; export function copy$Cell_u32(v: rs.Cell): rs.Cell { - return rs.objectCopy(v); + return new rs.Cell(v.v); } export function copy$Option_Cell_u32(v: rs.Cell | undefined): rs.Cell | undefined { @@ -113,6 +113,13 @@ export function copy$mem_MaybeUninit_tuple2_u8_u8(v: [rs.u8, rs.u8]): [rs.u8, rs return [v[0], v[1]]; } +export function copy$ops_Range_Cell_u32(v: rs.Range>): rs.Range> { + const c = rs.objectCopy(v); + c.start = copy$Cell_u32(c.start); + c.end = copy$Cell_u32(c.end); + return c; +} + export function copy$pointers_Aimed_Cell_u8(v: Aimed.Aimed>): Aimed.Aimed> { return { at: v.at as rs.Place>, seen: v.seen as rs.Place>, held: v.held as rs.Place>, maybe: v.maybe as rs.Place> | undefined, tagged: copy$tuple2_mutPtr_Cell_u8_u8(v.tagged as [rs.Place>, rs.u8]), to: v.to, read: (v.read as (a0: rs.Place>) => rs.u8) as (a0: rs.Ptr>) => rs.u8 }; } diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index d7dc8220e..d727b49b9 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$cell_OnceCell_Cell_u32, copy$cell_RefCell_Cell_u32, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; +import { copy$Cell_u32, copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$cell_OnceCell_Cell_u32, copy$cell_RefCell_Cell_u32, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$ops_Range_Cell_u32, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; import { thenSome$1gvhopg, then$01qpu7s, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, map$1px2rk4 as option_map$1px2rk4, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$18adzvj as option_mapOr$18adzvj, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; import type * as Aimed from "./pointers.Aimed.ts"; @@ -3510,6 +3510,18 @@ export function inlineMoved(r: rs.RefCell>, o: rs.OnceCell>, c: rs.Cell): [rs.Range>, rs.Cell] { + const old: [rs.Range>, rs.Cell] = [copy$ops_Range_Cell_u32(rs.placeIn(r).v), copy$Cell_u32(rs.placeIn(c).v)]; + rs.cellSet(old[0].start, rs.u32Add(old[0].start.v, 9)); + rs.cellSet(old[0].end, rs.u32Add(old[0].end.v, 9)); + rs.cellSet(old[1], rs.u32Add(old[1].v, 9)); + return old; +} + /** * Values holding a library object, moved out through pointers made each way, at any * depth, and then written in place: what was moved out keeps what it held. @@ -3602,6 +3614,21 @@ export function movedOut(data: rs.ByteSlice, out: rs.ByteVec): void { return a02.v; }) & 0xff); } + const r2: rs.Range> = new rs.Range(new rs.Cell(b as rs.u32), new rs.Cell(rs.u32Add(b as rs.u32, 1))); + let x: rs.u32 = rs.u32Add(b as rs.u32, 2); + const _167: rs.Cell = rs.cellOfPlace(rs.place(() => x, (value2) => x = value2)); + const _165: [rs.Range>, rs.Cell] = endsMoved(r2, _167); + const oldR2: rs.Range> = _165[0]; + const oldC: rs.Cell = _165[1]; + const iter8: rs.Iter>> = rs.iterSlice([r2, oldR2]); + while (true) { + const _174: rs.Range> | rs.Done = iter8.next(); + if (_174 === rs.DONE) { + break; + } + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(_174.start.v & 0xff, _174.end.v & 0xff)), undefined, true); + } + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(oldC.v & 0xff, x & 0xff)), undefined, true); } } diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index e0b95024e..583632ad8 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -13,6 +13,7 @@ use std::cell::{Cell, OnceCell, RefCell, UnsafeCell}; use std::mem::{self, MaybeUninit}; +use std::ops; use std::ptr::{self, NonNull}; use std::rc::Rc; use std::sync::Arc; @@ -2737,6 +2738,16 @@ fn inline_moved(r: &RefCell>, o: &OnceCell>) -> (RefCell>, c: &Cell) -> (ops::Range>, Cell) { + let old = unsafe { (ptr::read(r), ptr::read(c)) }; + old.0.start.set(old.0.start.get() + 9); + old.0.end.set(old.0.end.get() + 9); + old.1.set(old.1.get() + 9); + old +} + /// Values holding a library object, moved out through pointers made each way, at any /// depth, and then written in place: what was moved out keeps what it held. pub fn moved_out(data: &[u8], out: &mut Vec) { @@ -2791,6 +2802,13 @@ pub fn moved_out(data: &[u8], out: &mut Vec) { for o in [&o, &old_o] { out.push(o.get().map_or(0, Cell::get) as u8); } + let r = Cell::new(u32::from(b))..Cell::new(u32::from(b) + 1); + let mut x = u32::from(b) + 2; + let (old_r, old_c) = ends_moved(&r, Cell::from_mut(&mut x)); + for r in [&r, &old_r] { + out.extend([r.start.get() as u8, r.end.get() as u8]); + } + out.extend([old_c.get() as u8, x as u8]); } } diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 5fcc66d66..7c5eae7ea 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -66,7 +66,14 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { return self.writes.contains(&self.writes.into, false, self.tcx, ty) || self.addressed(ty) || !ty.is_freeze(self.tcx, self.typing_env()) - || self.inline_held(ty).is_some_and(|(_, held)| self.changes(held)); + || { + let (fields, unknown) = self.inline_held(ty); + fields + .into_iter() + .map(|(_, t)| t) + .chain(unknown) + .any(|t| self.changes(t)) + }; } Some(_) => return false, None if self.own(adt.did()) && adt.all_fields().next().is_some() => { @@ -80,19 +87,40 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { || parts.into_iter().any(|t| self.changes(t)) } - /// The field of a library object `ty` that holds its value inline rather than on the - /// heap, which a copy of the object one object deep shares, and the type held there: a - /// cell's or a lock's value, or a one-time cell's, which holds nothing until set. - fn inline_held(&self, ty: Ty<'tcx>) -> Option<(&'static str, Ty<'tcx>)> { + /// What a library object `ty` holds inline rather than on the heap, which a copy of the + /// object one object deep shares: each field of its JS object holding such a value and + /// the type held there, a cell's, a lock's, a range's ends, or a one-time cell's, which + /// holds nothing until set; and the types it holds where no copy reaches, such as a + /// hash map's hasher or what a `LazyCell` keeps private. A collection's items are on + /// the heap, which Rust's read shares too. + fn inline_held(&self, ty: Ty<'tcx>) -> (Vec<(&'static str, Ty<'tcx>)>, Vec>) { let ty::Adt(adt, args) = ty.kind() else { - return None; + return (Vec::new(), Vec::new()); }; - match Std::of(self.tcx, adt.did())? { - Std::Cell => Some(("v", args.type_at(0))), - Std::RefCell | Std::Mutex => Some(("value", args.type_at(0))), - Std::OnceCell | Std::OnceLock => Some(("v", Ty::new_option(self.tcx, args.type_at(0)))), - _ => None, - } + let at = |i: usize| args.type_at(i); + let fields = match Std::of(self.tcx, adt.did()) { + Some(Std::Cell) => vec![("v", at(0))], + Some(Std::RefCell | Std::Mutex) => vec![("value", at(0))], + Some(Std::OnceCell | Std::OnceLock) => vec![("v", Ty::new_option(self.tcx, at(0)))], + Some(Std::Range) => vec![("start", at(0)), ("end", at(0))], + Some(Std::RangeInclusive) => vec![("lo", at(0)), ("hi", at(0))], + Some(Std::RangeFrom) => vec![("start", at(0))], + Some( + Std::Vec + | Std::String + | Std::VecDeque + | Std::LinkedList + | Std::BinaryHeap + | Std::BTreeMap + | Std::BTreeSet + | Std::HashTable + | Std::BytesMut, + ) => Vec::new(), + Some(Std::HashMap) => return (Vec::new(), vec![at(2)]), + Some(Std::HashSet) => return (Vec::new(), vec![at(1)]), + _ => return (Vec::new(), args.types().collect()), + }; + (fields, Vec::new()) } /// Whether the runs before compared pointers to a `ty`, or to a type it holds by value, @@ -346,19 +374,32 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } // A library object, which [`Self::changes`] says a whole write lands in, and a // copy of what it holds inline. - Some(_) => { + Some(std) => { + let (fields, unknown) = self.inline_held(ty); + if unknown.into_iter().any(|t| self.changes(t)) { + self.err( + span, + format!("a read moving out a `{ty}`, which holds inline a value JS can't copy apart"), + ); + } + // A cell of its own, as `Cell::from_mut`'s place reads and writes another's. + if std == Std::Cell { + let held = self.snapshot(v().member("v"), args.type_at(0), span); + return ret(Expr::New(Box::new(Expr::Rs("Cell".into())), vec![held])); + } + let fields: Vec<_> = fields.into_iter().filter(|&(_, t)| self.changes(t)).collect(); let copy = Expr::rs("objectCopy", vec![v()]); - let held = self.inline_held(ty).filter(|&(_, held)| self.changes(held)); - let Some((field, held)) = held else { + if fields.is_empty() { return ret(copy); - }; + } let c = || Expr::ident("c"); - let read = self.snapshot(c().member(field), held, span); - vec![ - StmtKind::decl(false, "c", None, Some(copy)).at(None), - StmtKind::Expr(c().member(field).assign(read)).at(None), - StmtKind::Return(Some(c())).at(None), - ] + let mut body = vec![StmtKind::decl(false, "c", None, Some(copy)).at(None)]; + for (field, t) in fields { + let read = self.snapshot(c().member(field), t, span); + body.push(StmtKind::Expr(c().member(field).assign(read)).at(None)); + } + body.push(StmtKind::Return(Some(c())).at(None)); + body } _ => { let info = self.adt(*adt, span); From 501e113232da0ad70f55d5adbcda7836f050cc6a Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 08:31:11 -0800 Subject: [PATCH 18/29] fix(ts): refuse moving out a library object whose inline state only its runtime class knows A read that moves out a library object outside the known list (cells, locks, one-time cells, ranges, collections) now refuses where it would copy one, instead of copying it one object deep and sharing what its runtime class keeps inline, such as a LazyCell's initializer or a Peekable's peeked item. Co-Authored-By: Claude Opus 5.5 --- .../lang/conversions/expected/conversions.ts | 8 ++-- ts/fixtures/lang/slices/expected/lib.ts | 4 ++ ts/fixtures/lang/slices/expected/slices.ts | 4 +- ts/fixtures/reject/census.txt | 2 + ts/fixtures/reject/expected.txt | 2 + ts/fixtures/reject/src/alloc.rs | 19 ++++++++ ts/fixtures/reject/ts.toml | 2 + ts/src/lower/copy.rs | 44 +++++++++++-------- 8 files changed, 61 insertions(+), 24 deletions(-) diff --git a/ts/fixtures/lang/conversions/expected/conversions.ts b/ts/fixtures/lang/conversions/expected/conversions.ts index 09bcbafa8..38aca770d 100644 --- a/ts/fixtures/lang/conversions/expected/conversions.ts +++ b/ts/fixtures/lang/conversions/expected/conversions.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$array_Option_u16, copy$array_array0_u8, copy$array_array1_u8, copy$array_array2_u32, copy$array_array3_u8, copy$array_array40_u8, copy$array_u32, copy$array_u64, copy$array_u8, copy$conversions_Logged, copy$conversions_Named, drop$tuple2_conversions_Logged_u8 } from "./lib.ts"; +import { copy$String, copy$Vec_u32, copy$Vec_u8, copy$array_Option_u16, copy$array_array0_u8, copy$array_array1_u8, copy$array_array2_u32, copy$array_array3_u8, copy$array_array40_u8, copy$array_u32, copy$array_u64, copy$array_u8, copy$conversions_Logged, copy$conversions_Named, drop$tuple2_conversions_Logged_u8 } from "./lib.ts"; import * as Held from "./conversions.Held.ts"; import * as Kept from "./conversions.Kept.ts"; import * as Logged from "./conversions.Logged.ts"; @@ -843,7 +843,7 @@ export function lentParam$String(to: rs.String, data: rs.ByteSlice, out: rs.Byte rs.idx(s, 0); const borrowed7 = s.data; const borrowed8 = s.start; - put$conversions_Value_String(rs.cellReplace(rs.cellOfArraySlice(rs.arraySliceOne(rs.sliceOfPlace(rs.place(() => borrowed7[borrowed8], (value5) => borrowed7[borrowed8] = value5)), 1), rs.objectCopy), [[make$conversions_Value_String(data, 28)]])[0][0], out); + put$conversions_Value_String(rs.cellReplace(rs.cellOfArraySlice(rs.arraySliceOne(rs.sliceOfPlace(rs.place(() => borrowed7[borrowed8], (value5) => borrowed7[borrowed8] = value5)), 1), copy$String), [[make$conversions_Value_String(data, 28)]])[0][0], out); rs.idx(s, 0); s.data[s.start] = rs.stringCloneFrom(s.data[s.start], other); rs.idx(s, 0); @@ -905,7 +905,7 @@ export function lentParam$Vec_u32(to: rs.Vec, data: rs.ByteSlice, out: r rs.idx(s, 0); const borrowed7 = s.data; const borrowed8 = s.start; - put$conversions_Value_Vec_u32(rs.cellReplace(rs.cellOfArraySlice(rs.arraySliceOne(rs.sliceOfPlace(rs.place(() => borrowed7[borrowed8], (value5) => borrowed7[borrowed8] = value5)), 1), rs.objectCopy), [[make$conversions_Value_Vec_u32(data, 28)]])[0][0], out); + put$conversions_Value_Vec_u32(rs.cellReplace(rs.cellOfArraySlice(rs.arraySliceOne(rs.sliceOfPlace(rs.place(() => borrowed7[borrowed8], (value5) => borrowed7[borrowed8] = value5)), 1), copy$Vec_u32), [[make$conversions_Value_Vec_u32(data, 28)]])[0][0], out); rs.idx(s, 0); rs.vecCloneFrom(s.data[s.start], other, 4); rs.idx(s, 0); @@ -967,7 +967,7 @@ export function lentParam$Vec_u8(to: rs.ByteVec, data: rs.ByteSlice, out: rs.Byt rs.idx(s, 0); const borrowed7 = s.data; const borrowed8 = s.start; - put$conversions_Value_Vec_u8(rs.cellReplace(rs.cellOfArraySlice(rs.arraySliceOne(rs.sliceOfPlace(rs.place(() => borrowed7[borrowed8], (value5) => borrowed7[borrowed8] = value5)), 1), rs.objectCopy), [[make$conversions_Value_Vec_u8(data, 28)]])[0][0], out); + put$conversions_Value_Vec_u8(rs.cellReplace(rs.cellOfArraySlice(rs.arraySliceOne(rs.sliceOfPlace(rs.place(() => borrowed7[borrowed8], (value5) => borrowed7[borrowed8] = value5)), 1), copy$Vec_u8), [[make$conversions_Value_Vec_u8(data, 28)]])[0][0], out); rs.idx(s, 0); rs.byteVecCloneFrom(s.data[s.start], other); rs.idx(s, 0); diff --git a/ts/fixtures/lang/slices/expected/lib.ts b/ts/fixtures/lang/slices/expected/lib.ts index abd0a5bb5..858b97cef 100644 --- a/ts/fixtures/lang/slices/expected/lib.ts +++ b/ts/fixtures/lang/slices/expected/lib.ts @@ -13,6 +13,10 @@ export function copy$Option_array2_u8(v: Uint8Array | undefined): Uint8Array | u return v === undefined ? v : copy$array_u8(v); } +export function copy$Vec_u8(v: rs.ByteVec): rs.ByteVec { + return rs.objectCopy(v); +} + export function copy$array_array2_array2_u8(v: Uint8Array[][]): Uint8Array[][] { return v.map(copy$array_array2_u8); } diff --git a/ts/fixtures/lang/slices/expected/slices.ts b/ts/fixtures/lang/slices/expected/slices.ts index 4b89d5fc8..625e354c4 100644 --- a/ts/fixtures/lang/slices/expected/slices.ts +++ b/ts/fixtures/lang/slices/expected/slices.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$Option_array2_u16, copy$Option_array2_u8, copy$array_array2_array2_u8, copy$array_array2_u16, copy$array_array2_u8, copy$array_u16, copy$array_u8, copy$mem_MaybeUninit_array_u8, copy$slices_Pair, drop$tuple2_Vec_slices_Noisy_Vec_u8 } from "./lib.ts"; +import { copy$Option_array2_u16, copy$Option_array2_u8, copy$Vec_u8, copy$array_array2_array2_u8, copy$array_array2_u16, copy$array_array2_u8, copy$array_u16, copy$array_u8, copy$mem_MaybeUninit_array_u8, copy$slices_Pair, drop$tuple2_Vec_slices_Noisy_Vec_u8 } from "./lib.ts"; import { isSomeAnd$1ouf3e6, unwrapOr$1la41a6 as option_unwrapOr$1la41a6, unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$1n9qa5p as option_unwrapOrDefault$1n9qa5p, unwrapOrDefault$0h81s8g as option_unwrapOrDefault$0h81s8g, map$13tab2s as option_map$13tab2s, map$0duedp1 as option_map$0duedp1, map$0q6fk7x as option_map$0q6fk7x, map$1uud0v7 as option_map$1uud0v7, map$1j0y4j0 as option_map$1j0y4j0, map$0vk4zok as option_map$0vk4zok, map$1aqm6sp as option_map$1aqm6sp, map$04ry0e1 as option_map$04ry0e1, mapOr$135q041 as option_mapOr$135q041, or$0as8tia as option_or$0as8tia, zip$0wdt346, clone$1omlcob as option_clone$1omlcob } from "./core/option.ts"; import * as Counted from "./slices.Counted.ts"; import * as Item from "./slices.Item.ts"; @@ -2970,7 +2970,7 @@ export function arrayForms(kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): vo rs.idx(runs2, 0); const _354: rs.ByteVec[] = [rs.byteVecFrom(Uint8Array.of(byte(1), byte(2)))]; rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(rs.replaceIn(rs.sliceView(rs.arraySliceAt(runs2, 0)), _354, 1)[0])), undefined, true); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(rs.cellReplace(rs.cellOfArraySlice(rs.arraySliceOne(rs.sliceOfPlace(rs.place(() => item, (value2) => item = value2)), 1), rs.objectCopy), [[rs.byteVecFrom(Uint8Array.of(byte(3)))]])[0][0])), undefined, true); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(rs.cellReplace(rs.cellOfArraySlice(rs.arraySliceOne(rs.sliceOfPlace(rs.place(() => item, (value2) => item = value2)), 1), copy$Vec_u8), [[rs.byteVecFrom(Uint8Array.of(byte(3)))]])[0][0])), undefined, true); rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(item)), undefined, true); } } diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 05998c633..864b3f227 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -11,7 +11,9 @@ fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` to one to `pointers::Halves`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place +fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:peeked_moved+3:26: a read moving out a `std::iter::Peekable>>`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:12: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:13: unsupported call to `std::alloc::alloc_zeroed` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 9a6ec47ff..79fabfaa5 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -15,7 +15,9 @@ fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` to one to `pointers::Halves`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place +fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:peeked_moved+3:26: a read moving out a `std::iter::Peekable>>`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:12: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:13: unsupported call to `std::alloc::alloc_zeroed` diff --git a/ts/fixtures/reject/src/alloc.rs b/ts/fixtures/reject/src/alloc.rs index 127ba0ea8..f4459cafa 100644 --- a/ts/fixtures/reject/src/alloc.rs +++ b/ts/fixtures/reject/src/alloc.rs @@ -31,6 +31,25 @@ pub fn infallible_cell(r: &Result, std::convert::Infallible unsafe { std::ptr::from_ref(r).cast_mut().write(Ok(std::cell::Cell::new(19))) } } +/// A `LazyCell` moved out through a pointer before its initializer, which holds a cell, +/// runs: the closure is state only `rs.LazyCell` knows, which a copy can't reach. +pub fn lazy_moved(cell: std::cell::Cell) -> u32 { + let lazy = std::cell::LazyCell::new(move || cell.get()); + let copy = unsafe { std::ptr::read(&lazy) }; + std::mem::forget(lazy); + *copy +} + +/// A `Peekable` moved out through a pointer with a cell peeked, which only its runtime +/// class holds. +pub fn peeked_moved(cells: Vec>) -> u32 { + let mut iter = cells.into_iter().peekable(); + iter.peek(); + let mut copy = unsafe { std::ptr::read(&iter) }; + std::mem::forget(iter); + copy.next().map_or(0, |cell| cell.get()) +} + /// A pointer from a reference the body is given, which JS keeps in no array, moved past /// it. pub fn past_reference(r: &u32) -> u32 { diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 221faa76f..96c6f563c 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -5,7 +5,9 @@ roots = [ "alloc::allocated", "alloc::bytes_as_values", "alloc::infallible_cell", + "alloc::lazy_moved", "alloc::past_reference", + "alloc::peeked_moved", "alloc::reinterpreted", "alloc::zeroed", "bound_ranges", diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 16d4f6972..622bb1ba2 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -66,13 +66,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { return self.writes.contains(&self.writes.into, false, self.tcx, ty) || self.addressed(ty) || !ty.is_freeze(self.tcx, self.typing_env()) - || { - let (fields, unknown) = self.inline_held(ty); - fields - .into_iter() - .map(|(_, t)| t) - .chain(unknown) - .any(|t| self.changes(t)) + || match self.inline_held(ty) { + Some((fields, unreachable)) => { + fields.into_iter().map(|(_, t)| t).chain(unreachable).any(|t| self.changes(t)) + } + // Any type it is over may be held inline, which its copy refuses. + None => args.types().any(|t| self.changes(t)), }; } Some(_) => return false, @@ -90,12 +89,13 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// What a library object `ty` holds inline rather than on the heap, which a copy of the /// object one object deep shares: each field of its JS object holding such a value and /// the type held there, a cell's, a lock's, a range's ends, or a one-time cell's, which - /// holds nothing until set; and the types it holds where no copy reaches, such as a - /// hash map's hasher or what a `LazyCell` keeps private. A collection's items are on - /// the heap, which Rust's read shares too. - fn inline_held(&self, ty: Ty<'tcx>) -> (Vec<(&'static str, Ty<'tcx>)>, Vec>) { + /// holds nothing until set; and the types it holds where no copy reaches, a hash map's + /// hasher. A collection's items are on the heap, which Rust's read shares too. `None` + /// for any other library object, such as a `LazyCell` or a `Peekable`, whose inline + /// state only its runtime class knows, so a read that would copy one is refused. + fn inline_held(&self, ty: Ty<'tcx>) -> Option<(Vec<(&'static str, Ty<'tcx>)>, Vec>)> { let ty::Adt(adt, args) = ty.kind() else { - return (Vec::new(), Vec::new()); + return None; }; let at = |i: usize| args.type_at(i); let fields = match Std::of(self.tcx, adt.did()) { @@ -116,11 +116,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::HashTable | Std::BytesMut, ) => Vec::new(), - Some(Std::HashMap) => return (Vec::new(), vec![at(2)]), - Some(Std::HashSet) => return (Vec::new(), vec![at(1)]), - _ => return (Vec::new(), args.types().collect()), + Some(Std::HashMap) => return Some((Vec::new(), vec![at(2)])), + Some(Std::HashSet) => return Some((Vec::new(), vec![at(1)])), + _ => return None, }; - (fields, Vec::new()) + Some((fields, Vec::new())) } /// Whether the runs before compared pointers to a `ty`, or to a type it holds by value, @@ -394,8 +394,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { // A library object, which [`Self::changes`] says a whole write lands in, and a // copy of what it holds inline. Some(std) => { - let (fields, unknown) = self.inline_held(ty); - if unknown.into_iter().any(|t| self.changes(t)) { + let Some((fields, unreachable)) = self.inline_held(ty) else { + self.err( + span, + format!( + "a read moving out a `{ty}`, a library object whose inline state only its runtime class knows, which JS can't copy apart" + ), + ); + return ret(v()); + }; + if unreachable.into_iter().any(|t| self.changes(t)) { self.err( span, format!("a read moving out a `{ty}`, which holds inline a value JS can't copy apart"), From 20c188dfde6b4e7070b9e68a5e864d34f58c30ef Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 08:40:51 -0800 Subject: [PATCH 19/29] fix(ts): copy a Weak moved out through a pointer as the handle it is A Weak holds only its pointer to what it shares, so a copy one object deep is Rust's bitwise read. smallvec's Drain and retain over Weak in moq need it. Co-Authored-By: Claude Opus 5.5 --- .../lang/pointers/expected/core/option.ts | 14 ++ ts/fixtures/lang/pointers/expected/lib.ts | 4 + .../pointers/expected/pointers.Drained.ts | 16 +- .../lang/pointers/expected/pointers.ts | 159 ++++++++++++++---- ts/fixtures/lang/pointers/src/pointers.rs | 26 +++ ts/src/lower/copy.rs | 6 +- 6 files changed, 187 insertions(+), 38 deletions(-) diff --git a/ts/fixtures/lang/pointers/expected/core/option.ts b/ts/fixtures/lang/pointers/expected/core/option.ts index 67a3f757d..92ed51a7c 100644 --- a/ts/fixtures/lang/pointers/expected/core/option.ts +++ b/ts/fixtures/lang/pointers/expected/core/option.ts @@ -76,6 +76,13 @@ export function map$1cfrse5(self: Loaded.Loaded | undefined, f: (a0: Loaded.Load return f(self); } +export function map$1ng7x7l(self: rs.Weak | undefined, f: (a0: rs.Weak) => rs.Weak): rs.Weak | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$070fw7m(self: rs.u32 | undefined, f: (a0: rs.u32) => rs.u32): rs.u32 | undefined { if (self === undefined) { return undefined; @@ -111,6 +118,13 @@ export function mapOr$18adzvj(self: rs.Cell | undefined, default_: rs.u3 return self.v; } +export function mapOr$0emjeap(self: rs.Arc | undefined, default_: rs.u8, f: (a0: rs.Arc) => rs.u8): rs.u8 { + if (self === undefined) { + return default_; + } + return f(self); +} + export function mapOr$173j2gr(self: [rs.u8, rs.Arc] | undefined, default_: rs.u8, f: (a0: [rs.u8, rs.Arc]) => rs.u8): rs.u8 { if (self === undefined) { return default_; diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index 7d92b735d..ca433f73b 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -200,6 +200,10 @@ export function copy$pointers_Twin(v: Twin.Twin): Twin.Twin { return { _0: v._0, _1: v._1 }; } +export function copy$sync_Weak_u32(v: rs.Weak): rs.Weak { + return rs.objectCopy(v); +} + export function copy$tuple2_Vec_u8_u8(v: [rs.ByteVec, rs.u8]): [rs.ByteVec, rs.u8] { return [copy$Vec_u8(v[0]), v[1]]; } diff --git a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts index 344d8eaf7..5e2a38872 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts @@ -1,8 +1,8 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$String, copy$Vec_u8 } from "./lib.ts"; -import { map$00buaky as option_map$00buaky, map$1lzqy5c as option_map$1lzqy5c, map$1h9tk16 as option_map$1h9tk16, map$070fw7m as option_map$070fw7m } from "./core/option.ts"; +import { copy$String, copy$Vec_u8, copy$sync_Weak_u32 } from "./lib.ts"; +import { map$00buaky as option_map$00buaky, map$1lzqy5c as option_map$1lzqy5c, map$1h9tk16 as option_map$1h9tk16, map$1ng7x7l as option_map$1ng7x7l, map$070fw7m as option_map$070fw7m } from "./core/option.ts"; /** * What smallvec's `Drain` is: the elements of a slice nothing will drop again, each read @@ -30,6 +30,12 @@ export function next$iter_Iterator_Vec_u8(self: Drained): rs.ByteVec })); } +export function next$iter_Iterator_sync_Weak_u32(self: Drained>): rs.Weak | rs.Done { + return rs.iterFromOption(option_map$1ng7x7l(rs.iterToOption((self.iter as rs.Iter>).next()), (r: rs.Weak): rs.Weak => { + return copy$sync_Weak_u32(r); + })); +} + export function next$iter_Iterator_u32(self: Drained): rs.u32 | rs.Done { return rs.iterFromOption(option_map$070fw7m(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.u32): rs.u32 => { return r; @@ -54,6 +60,12 @@ export function nextBack$iter_DoubleEndedIterator_Vec_u8(self: Drained>): rs.Weak | undefined { + return option_map$1ng7x7l(rs.iterToOption((self.iter as rs.Iter>).nextBack()), (r: rs.Weak): rs.Weak => { + return copy$sync_Weak_u32(r); + }); +} + export function nextBack$iter_DoubleEndedIterator_u32(self: Drained): rs.u32 | undefined { return option_map$070fw7m(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.u32): rs.u32 => { return r; diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index e196f4cac..da39e1834 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -1,9 +1,9 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$Cell_u32, copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$cell_OnceCell_Cell_u32, copy$cell_RefCell_Cell_u32, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$ops_Range_Cell_u32, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; +import { copy$Cell_u32, copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$cell_OnceCell_Cell_u32, copy$cell_RefCell_Cell_u32, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$ops_Range_Cell_u32, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$sync_Weak_u32, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; import { thenSome$1gvhopg, then$01qpu7s, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; -import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, map$1px2rk4 as option_map$1px2rk4, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$18adzvj as option_mapOr$18adzvj, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; +import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, map$1px2rk4 as option_map$1px2rk4, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$18adzvj as option_mapOr$18adzvj, mapOr$0emjeap as option_mapOr$0emjeap, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; import type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; import type * as ByCall from "./pointers.ByCall.ts"; @@ -3058,6 +3058,33 @@ export function drained$Vec_u8(v: rs.Vec, bits: rs.u8): rs.Vec>, bits: rs.u8): rs.Vec> { + let next!: rs.Weak | undefined; + const v2: rs.Vec> = v; + const iter: Drained.Drained> = { iter: rs.iterSlice(v2.items) as rs.Iter>> }; + const out: rs.Vec> = new rs.Vec(); + let i: rs.i32 = 0; + while (true) { + if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { + next = rs.iterToOption(Drained.next$iter_Iterator_sync_Weak_u32(iter)); + } else { + next = Drained.nextBack$iter_DoubleEndedIterator_sync_Weak_u32(iter); + } + if (next !== undefined) { + out.items.push(next); + i = rs.i32Add(i, 1); + } else { + const v3: rs.Vec> = v2; + rs.vecSetLen(v3, 0, 4, (): rs.Weak => { + return (undefined as unknown) as rs.Weak; + }); + rs.dropVec(v3); + return out; + } + } +} + /** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ export function drained$u32(v: rs.Vec, bits: rs.u8): rs.Vec { let next!: rs.u32 | undefined; @@ -3154,6 +3181,31 @@ export function retained$Vec_u8_pointers_behind_refs_closure8(v: rs.Vec>, keep: (a0: rs.Weak) => boolean): void { + let _10!: rs.usize; + const len: rs.usize = v.items.length; + const p: rs.Place> = rs.vecAsPtr(v, 4); + let write: rs.usize = 0; + let iterStart: rs.usize = 0; + const iterEnd: rs.usize = len; + while ((_10 = iterStart++) < iterEnd) { + const _15: rs.Place> = rs.ptrAdd(p, _10); + if (!keep(_15.v)) { + _15.v[Symbol.dispose](); + } else { + rs.ptrCopy(_15, rs.ptrAdd(p, write), 1, copy$sync_Weak_u32); + write = rs.usizeAdd(write, 1); + } + } + rs.vecSetLen(v, write, 4, (): rs.Weak => { + return (undefined as unknown) as rs.Weak; + }); +} + /** * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then * drops or moves to the front. @@ -3278,8 +3330,8 @@ export function cellBehind(r: rs.Cell | undefined, how: rs.u8): rs.u32 { * from references to an `Arc`'s value, measured against each other. */ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { - let _133!: rs.usize; - let _173!: rs.u8; + let _175!: rs.usize; + let _215!: rs.u8; const bits: rs.u8 = option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0); const nums: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_2: rs.u8): rs.u32 => { return rs.u32Mul(_2 as rs.u32, 3); @@ -3321,12 +3373,12 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { return _26 & 0xff; }), undefined, true); const words2: rs.Vec = words; - retained$String_pointers_behind_refs_closure6(words2, (w3: rs.String): boolean => { - rs.stringPush(w3, 33); - return rs.usizeRem(w3.len, 2) === 0; + retained$String_pointers_behind_refs_closure6(words2, (w4: rs.String): boolean => { + rs.stringPush(w4, 33); + return rs.usizeRem(w4.len, 2) === 0; }); - rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(words2.items), (w4: rs.String): rs.Iter => { - return rs.strBytes(w4.v); + rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(words2.items), (w5: rs.String): rs.Iter => { + return rs.strBytes(w5.v); }, undefined, true), "next"); const runs2: rs.Vec = runs; retained$Vec_u8_pointers_behind_refs_closure8(runs2, (r2: rs.ByteVec): boolean => { @@ -3347,17 +3399,55 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { }), undefined, true); rs.dropVec(counted); rs.byteVecPush(out, dropped.v); - const nums3: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_28: rs.u8): rs.u32 => { - return _28 as rs.u32; + const arc: rs.Arc = new rs.Arc(bits as rs.u32); + const weaks: rs.Vec> = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_28: rs.u8): rs.Weak => { + if (rs.u8Rem(_28, 2) === 0) { + return new rs.Weak(arc); + } + return new rs.Weak(); + }), undefined, 4, true); + for (const w2 of drained$sync_Weak_u32(rs.vecFrom(weaks.items.map((a05: rs.Weak): rs.Weak => { + return rs.weakClone(a05); + })), bits).items) { + rs.byteVecPush(out, option_mapOr$0emjeap(rs.weakUpgrade(w2), 255, (n5: rs.Arc): rs.u8 => { + const _019: rs.u8 = n5.value & 0xff; + n5[Symbol.dispose](); + return _019; + })); + w2[Symbol.dispose](); + } + const weaks2: rs.Vec> = weaks; + let i: rs.u8 = 0; + retained$sync_Weak_u32_pointers_behind_refs_closure14(weaks2, (w6: rs.Weak): boolean => { + i = rs.u8Add(i, 1); + if (rs.u8Rem(i, 3) === 0) { + const _86: rs.Weak = new rs.Weak(arc); + w6[Symbol.dispose](); + Object.assign(w6, _86); + } + return rs.u8Rem(i, 2) === 0; + }); + rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(weaks2.items), (w7: rs.Weak): rs.u8 => { + return option_mapOr$0emjeap(rs.weakUpgrade(w7), 255, (n6: rs.Arc): rs.u8 => { + const _022: rs.u8 = n6.value & 0xff; + n6[Symbol.dispose](); + return _022; + }); + }), undefined, true); + rs.byteVecPush(out, arc.weak & 0xff); + rs.dropVec(weaks2); + rs.byteVecPush(out, arc.weak & 0xff); + const nums3: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_29: rs.u8): rs.u32 => { + return _29 as rs.u32; }), undefined, 4, true); if (rs.sliceFrom(nums3.items).length >= 2) { const first: rs.Place = rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 0)); rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, rs.usizeSub(nums3.items.length, 1))), first) & 0xff); - const _130: rs.usize = nums3.items.length; + const _172: rs.usize = nums3.items.length; let iterStart: rs.usize = 0; - const iterEnd: rs.usize = _130; - while ((_133 = iterStart++) < iterEnd) { - rs.byteVecPush(out, rs.ptrAdd(first, _133).v & 0xff); + const iterEnd: rs.usize = _172; + while ((_175 = iterStart++) < iterEnd) { + rs.byteVecPush(out, rs.ptrAdd(first, _175).v & 0xff); } rs.byteVecPush(out, rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v & 0xff); rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v = 9; @@ -3365,42 +3455,43 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { } const v: rs.ByteVec = rs.byteSliceToByteVec(data); rs.byteVecPush(out, vecLen(v) & 0xff); - const _164: rs.ByteVec = rs.iterToByteVec(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), undefined, true); - refilled(v, _164, bits); + const _206: rs.ByteVec = rs.iterToByteVec(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), undefined, true); + refilled(v, _206, bits); rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); let iterStart2: rs.u8 = 0; const iterEnd2: rs.u8 = 3; - while ((_173 = iterStart2++) < iterEnd2) { - const how: rs.u8 = _173; - const w2: rs.ByteVec = rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b6: rs.u8): rs.u8 => { - return b6 ^ how; + while ((_215 = iterStart2++) < iterEnd2) { + const how: rs.u8 = _215; + const w3: rs.ByteVec = rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b7: rs.u8): rs.u8 => { + return b7 ^ how; }), undefined, true); - const _185: rs.ByteVec = rs.byteVecOf(bits, rs.usizeAdd(how as rs.usize, 1)); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(replaced(v, w2, _185, how))), undefined, true); + const _227: rs.ByteVec = rs.byteVecOf(bits, rs.usizeAdd(how as rs.usize, 1)); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(replaced(v, w3, _227, how))), undefined, true); rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(w2)), undefined, true); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(w3)), undefined, true); rs.byteVecPush(out, 255); } - const iter6: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); + const iter7: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _200: [rs.usize, rs.u8] | rs.Done = iter6.next(); - if (_200 === rs.DONE) { + const _242: [rs.usize, rs.u8] | rs.Done = iter7.next(); + if (_242 === rs.DONE) { break; } - const i2: rs.usize = _200[0]; - const b: rs.u8 = _200[1]; - rs.byteVecPush(out, behind(b as rs.u32, i2 & 0xff) & 0xff); + const i3: rs.usize = _242[0]; + const b: rs.u8 = _242[1]; + rs.byteVecPush(out, behind(b as rs.u32, i3 & 0xff) & 0xff); rs.byteVecPush(out, cellBehind(then$01qpu7s(rs.u8Rem(b, 3) !== 0, (): rs.Cell => { return new rs.Cell(b as rs.u32); }), b) & 0xff); } - const arc: rs.Arc = new rs.Arc(bits as rs.u32); - const p: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); - const q: rs.Place = rs.arcRaw(arc, rs.arcPlace(arc), "Arc"); + const arc2: rs.Arc = new rs.Arc(bits as rs.u32); + const p: rs.Place = rs.arcRaw(arc2, rs.arcPlace(arc2), "Arc"); + const q: rs.Place = rs.arcRaw(arc2, rs.arcPlace(arc2), "Arc"); rs.byteVecPush(out, rs.ptrOffsetFrom(p, q) & 0xff); rs.byteVecPush(out, q.v & 0xff); - rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arcRaw(arc, rs.arcPlace(arc), "Arc"), p) & 0xff); + rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arcRaw(arc2, rs.arcPlace(arc2), "Arc"), p) & 0xff); rs.byteVecPush(out, from$convert_From_ref_pointers_Halves32_ptr_NonNull_u32({ first: bits as rs.u32, second: ((bits as rs.u32) ^ 85) >>> 0 }).v & 0xff); + arc2[Symbol.dispose](); arc[Symbol.dispose](); } diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index 583632ad8..2408bb83f 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2591,6 +2591,32 @@ pub fn behind_refs(data: &[u8], out: &mut Vec) { out.extend(counted.iter().map(|c| c.id)); drop(counted); out.push(dropped.get()); + // A `Weak` holds only its pointer to what it shares, which a read out copies as is, with + // one written whole in place. + let arc = Arc::new(u32::from(bits)); + let weaks: Vec> = data + .iter() + .map(|&b| match b % 2 { + 0 => Arc::downgrade(&arc), + _ => std::sync::Weak::new(), + }) + .collect(); + for w in drained(weaks.clone(), bits) { + out.push(w.upgrade().map_or(0xff, |n| *n as u8)); + } + let mut weaks = weaks; + let mut i = 0u8; + retained(&mut weaks, |w| { + i += 1; + if i % 3 == 0 { + *w = Arc::downgrade(&arc); + } + i % 2 == 0 + }); + out.extend(weaks.iter().map(|w| w.upgrade().map_or(0xff, |n| *n as u8))); + out.push(Arc::weak_count(&arc) as u8); + drop(weaks); + out.push(Arc::weak_count(&arc) as u8); // From a reference the body borrowed, where the element is. let mut nums: Vec = data.iter().map(|&b| u32::from(b)).collect(); diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 622bb1ba2..67e94b6db 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -90,7 +90,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// object one object deep shares: each field of its JS object holding such a value and /// the type held there, a cell's, a lock's, a range's ends, or a one-time cell's, which /// holds nothing until set; and the types it holds where no copy reaches, a hash map's - /// hasher. A collection's items are on the heap, which Rust's read shares too. `None` + /// hasher. A collection's items, and what a `Weak` points at, are on the heap, which + /// Rust's read shares too. `None` /// for any other library object, such as a `LazyCell` or a `Peekable`, whose inline /// state only its runtime class knows, so a read that would copy one is refused. fn inline_held(&self, ty: Ty<'tcx>) -> Option<(Vec<(&'static str, Ty<'tcx>)>, Vec>)> { @@ -114,7 +115,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::BTreeMap | Std::BTreeSet | Std::HashTable - | Std::BytesMut, + | Std::BytesMut + | Std::Weak, ) => Vec::new(), Some(Std::HashMap) => return Some((Vec::new(), vec![at(2)])), Some(Std::HashSet) => return Some((Vec::new(), vec![at(1)])), From f275248c268c485fad2908a35a9cc574f6592f79 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 09:11:18 -0800 Subject: [PATCH 20/29] chore(ts): recount the census, name the inline-state alias, format Co-Authored-By: Claude Opus 5.5 --- quest/m2/ts-model/README.md | 6 +++--- quest/m3/ts-unsafe/README.md | 2 +- ts/src/lower/copy.rs | 14 ++++++++++---- ts/src/lower/mir/rvalue.rs | 4 +++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index 6af9b4f93..a1b3f1575 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -42,13 +42,13 @@ 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-07: 51 refusals. `Timestamp` alone refuses nothing, and +The census at the pin on 2026-11-07: 45 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, all of it 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 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 moq rewrites or drops +refusals) translation refuses for good, and smallvec's union over a type parameter, its +`Array::Item`, and raw parts (5) wait on [unsafe](/quest/m3/ts-unsafe/README.md), so moq rewrites or drops them, as it 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/ts-unsafe/README.md b/quest/m3/ts-unsafe/README.md index 2255dde9d..0162130a6 100644 --- a/quest/m3/ts-unsafe/README.md +++ b/quest/m3/ts-unsafe/README.md @@ -70,7 +70,7 @@ construct still open at `b3df54b6c`, all in m3: [#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-07: model 46, kio 72, moq-net 348 refusals. +Census at the moq pin on 2026-11-07: model 45, kio 72, moq-net 344 refusals. Public API: the translator's output. diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 67e94b6db..15b5f2296 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -29,6 +29,10 @@ use super::{AdtRepr, Translator, field_name, names}; use crate::program::{SymKind, Symbol}; use crate::ts::{self, Expr, Key, Param, Stmt, StmtKind}; +/// What a library object holds inline: each field of its JS object holding a value and +/// the type held there, and the types it holds where no copy reaches. +type Inline<'tcx> = (Vec<(&'static str, Ty<'tcx>)>, Vec>); + impl<'a, 'tcx> Translator<'a, 'tcx> { /// Whether a `ty` is copied where Rust copies it: a `Copy` value that is, or holds by /// value, a JS object the crate's code writes into in place or compares pointers to. @@ -67,9 +71,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { || self.addressed(ty) || !ty.is_freeze(self.tcx, self.typing_env()) || match self.inline_held(ty) { - Some((fields, unreachable)) => { - fields.into_iter().map(|(_, t)| t).chain(unreachable).any(|t| self.changes(t)) - } + Some((fields, unreachable)) => fields + .into_iter() + .map(|(_, t)| t) + .chain(unreachable) + .any(|t| self.changes(t)), // Any type it is over may be held inline, which its copy refuses. None => args.types().any(|t| self.changes(t)), }; @@ -94,7 +100,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// Rust's read shares too. `None` /// for any other library object, such as a `LazyCell` or a `Peekable`, whose inline /// state only its runtime class knows, so a read that would copy one is refused. - fn inline_held(&self, ty: Ty<'tcx>) -> Option<(Vec<(&'static str, Ty<'tcx>)>, Vec>)> { + fn inline_held(&self, ty: Ty<'tcx>) -> Option> { let ty::Adt(adt, args) = ty.kind() else { return None; }; diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index fc745d502..bd8ffcfe6 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -194,7 +194,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let value = self.place_expr(place, span, out); match pointee { _ if crate::lower::pointer::slice_of_bytes(pointee) => Expr::rs("rawByteSliceOf", vec![value]), - _ if crate::lower::pointer::slice_of_arrays(pointee) => Expr::rs("rawArraySliceOf", vec![value]), + _ if crate::lower::pointer::slice_of_arrays(pointee) => { + Expr::rs("rawArraySliceOf", vec![value]) + } _ => Expr::rs("rawSliceOf", vec![value]), } } From b117c7e5029a1704c4f0c65b4f7add68a3b266e4 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 10:05:38 -0800 Subject: [PATCH 21/29] fix(ts): copy a Vec's or VecDeque's storage on a move-out read, clear stale fields on a whole write, refuse unfixed offsets from a lone place A JS Vec keeps its length as its items array's, and a VecDeque clears the slot it pops, so a copy sharing that array saw the original's set_len or pop_front. ptr::write left a field the new value lacks, such as a HashMap's capacity. An offset the body doesn't fix moved a pointer from a reference out of its array of one at run time. Co-Authored-By: Claude Opus 5.5 --- .../lang/pointers/expected/core/option.ts | 7 +++ ts/fixtures/lang/pointers/expected/lib.ts | 12 ++++ .../lang/pointers/expected/pointers.ts | 52 +++++++++++++++- ts/fixtures/lang/pointers/src/pointers.rs | 31 ++++++++++ ts/fixtures/reject/census.txt | 1 + ts/fixtures/reject/expected.txt | 1 + ts/fixtures/reject/src/alloc.rs | 6 ++ ts/fixtures/reject/ts.toml | 1 + ts/runtime/rs.ts | 8 ++- ts/src/lower/copy.rs | 60 +++++++++++++------ ts/src/lower/mir/origin.rs | 13 +++- 11 files changed, 168 insertions(+), 24 deletions(-) diff --git a/ts/fixtures/lang/pointers/expected/core/option.ts b/ts/fixtures/lang/pointers/expected/core/option.ts index 92ed51a7c..9ac98ea05 100644 --- a/ts/fixtures/lang/pointers/expected/core/option.ts +++ b/ts/fixtures/lang/pointers/expected/core/option.ts @@ -139,6 +139,13 @@ export function mapOr$0smr0aj(self: rs.u16 | undefined, default_: Uint8Array, _: return rs.u16ToBeBytes(self); } +export function mapOr$07pvkk0(self: rs.u32 | undefined, default_: rs.u8, f: (a0: rs.u32) => rs.u8): rs.u8 { + if (self === undefined) { + return default_; + } + return f(self); +} + export function filter$0wvwd70(self: rs.u32 | undefined, predicate: (a0: rs.u32) => boolean): rs.u32 | undefined { if (self !== undefined) { const x: rs.u32 = self; diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index ca433f73b..3e82145de 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -45,6 +45,12 @@ export function copy$String(v: rs.String): rs.String { return rs.objectCopy(v); } +export function copy$Vec_u32(v: rs.Vec): rs.Vec { + const c = rs.objectCopy(v); + c.items = c.items.slice(); + return c; +} + export function copy$Vec_u8(v: rs.ByteVec): rs.ByteVec { return rs.objectCopy(v); } @@ -85,6 +91,12 @@ export function copy$cell_RefCell_Cell_u32(v: rs.RefCell>): rs.R return c; } +export function copy$collections_VecDeque_u32(v: rs.VecDeque): rs.VecDeque { + const c = rs.objectCopy(v); + c.b = c.b.slice(); + return c; +} + export function copy$mem_MaybeUninit_Vec_u8(v: rs.ByteVec): rs.ByteVec { if (v === undefined) { return v; diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index da39e1834..3c2680840 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -1,9 +1,9 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$Cell_u32, copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$cell_OnceCell_Cell_u32, copy$cell_RefCell_Cell_u32, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$ops_Range_Cell_u32, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$sync_Weak_u32, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; +import { copy$Cell_u32, copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u32, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$cell_OnceCell_Cell_u32, copy$cell_RefCell_Cell_u32, copy$collections_VecDeque_u32, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$ops_Range_Cell_u32, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$sync_Weak_u32, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; import { thenSome$1gvhopg, then$01qpu7s, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; -import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, map$1px2rk4 as option_map$1px2rk4, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$18adzvj as option_mapOr$18adzvj, mapOr$0emjeap as option_mapOr$0emjeap, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; +import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, map$1px2rk4 as option_map$1px2rk4, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$18adzvj as option_mapOr$18adzvj, mapOr$0emjeap as option_mapOr$0emjeap, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, mapOr$07pvkk0 as option_mapOr$07pvkk0, filter$0wvwd70 } from "./core/option.ts"; import type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; import type * as ByCall from "./pointers.ByCall.ts"; @@ -3601,6 +3601,28 @@ export function inlineMoved(r: rs.RefCell>, o: rs.OnceCell, q: rs.VecDeque): [rs.Vec, rs.VecDeque, rs.u32 | undefined] { + const v2: rs.Vec = v; + const oldV: rs.Vec = copy$Vec_u32(v2); + rs.vecSetLen(v2, 0, 4, (): rs.u32 => { + return (undefined as unknown) as rs.u32; + }); + return [oldV, copy$collections_VecDeque_u32(q), rs.dequePopFront(q)] as [rs.Vec, rs.VecDeque, rs.u32 | undefined]; +} + +/** + * A `HashMap` written whole through a pointer from the `&mut` given, which holds what the + * new one does, its capacity included. + */ +export function mapRewritten(r: rs.HashMap): void { + rs.placeIn(r).v = rs.hashNew() as rs.HashMap; +} + /** * A range of cells and a cell `Cell::from_mut` made of a variable's place, moved out * through pointers from shared references to them, whose cells are then set. @@ -3721,6 +3743,32 @@ export function movedOut(data: rs.ByteSlice, out: rs.ByteVec): void { } rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(oldC.v & 0xff, x & 0xff)), undefined, true); } + const nums: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_23: rs.u8): rs.u32 => { + return _23 as rs.u32; + }), undefined, 4, true); + const _199: [rs.Vec, rs.VecDeque, rs.u32 | undefined] = shrunkMoved(rs.vecFrom(nums.items.slice()), rs.dequeFromVec(rs.iterToVec(rs.iterSlice(nums.items), undefined, 4, true), 4) as rs.VecDeque); + const oldV: rs.Vec = _199[0]; + const oldQ: rs.VecDeque = _199[1]; + const popped: rs.u32 | undefined = _199[2]; + rs.byteVecPush(out, oldV.items.length & 0xff); + rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(oldV.items), (_24: rs.u32): rs.u8 => { + return _24 & 0xff; + }), undefined, true); + rs.byteVecPush(out, option_mapOr$07pvkk0(popped, 255, (n3: rs.u32): rs.u8 => { + return n3 & 0xff; + })); + rs.byteVecPush(out, oldQ.n & 0xff); + rs.iterExtendBytes(out, rs.iterMap(rs.dequeIter(oldQ), (_27: rs.u32): rs.u8 => { + return _27 & 0xff; + }), undefined, true); + const map: rs.HashMap = rs.hashWithCapacity(rs.usizeMul(data.length, 8), 117440512) as rs.HashMap; + const _232: rs.Iter<[rs.u32, rs.u32]> = rs.iterMap(rs.iterSlice(nums.items), (_29: rs.u32): [rs.u32, rs.u32] => { + const n5: rs.u32 = _29; + return [n5, n5] as [rs.u32, rs.u32]; + }); + rs.hashExtend(map, _232, undefined, undefined, undefined, 117440512); + mapRewritten(map); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(rs.hashLen(map) & 0xff, rs.hashCapacity(map) & 0xff)), undefined, true); } /** diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index 2408bb83f..039308d4b 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2764,6 +2764,26 @@ fn inline_moved(r: &RefCell>, o: &OnceCell>) -> (RefCell, mut q: std::collections::VecDeque) -> (Vec, std::collections::VecDeque, Option) { + let mut v = mem::ManuallyDrop::new(v); + let old_v = unsafe { ptr::read(&*v) }; + unsafe { v.set_len(0) }; + let old_q = unsafe { ptr::read(&q) }; + let popped = q.pop_front(); + mem::forget(q); + (old_v, old_q, popped) +} + +/// A `HashMap` written whole through a pointer from the `&mut` given, which holds what the +/// new one does, its capacity included. +#[inline(never)] +fn map_rewritten(r: &mut std::collections::HashMap) { + unsafe { ptr::write(ptr::from_mut(r), std::collections::HashMap::new()) } +} + /// A range of cells and a cell `Cell::from_mut` made of a variable's place, moved out /// through pointers from shared references to them, whose cells are then set. fn ends_moved(r: &ops::Range>, c: &Cell) -> (ops::Range>, Cell) { @@ -2836,6 +2856,17 @@ pub fn moved_out(data: &[u8], out: &mut Vec) { } out.extend([old_c.get() as u8, x as u8]); } + let nums: Vec = data.iter().map(|&b| u32::from(b)).collect(); + let (old_v, old_q, popped) = shrunk_moved(nums.clone(), nums.iter().copied().collect()); + out.push(old_v.len() as u8); + out.extend(old_v.iter().map(|&n| n as u8)); + out.push(popped.map_or(0xff, |n| n as u8)); + out.push(old_q.len() as u8); + out.extend(old_q.iter().map(|&n| n as u8)); + let mut map = std::collections::HashMap::::with_capacity(data.len() * 8); + map.extend(nums.iter().map(|&n| (n, n))); + map_rewritten(&mut map); + out.extend([map.len() as u8, map.capacity() as u8]); } /// Pointers to slices cast to ones to arrays of their elements, of bytes and of numbers: diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 1df7b50f9..7c962d6f8 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -12,6 +12,7 @@ fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart +fixtures/reject/src/alloc.rs:offset_reference+1:34: a pointer moved by an offset the body doesn't fix from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:peeked_moved+3:26: a read moving out a `std::iter::Peekable>>`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 79fabfaa5..001e9e243 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -16,6 +16,7 @@ fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart +fixtures/reject/src/alloc.rs:offset_reference+1:34: a pointer moved by an offset the body doesn't fix from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:peeked_moved+3:26: a read moving out a `std::iter::Peekable>>`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another diff --git a/ts/fixtures/reject/src/alloc.rs b/ts/fixtures/reject/src/alloc.rs index f4459cafa..98b108147 100644 --- a/ts/fixtures/reject/src/alloc.rs +++ b/ts/fixtures/reject/src/alloc.rs @@ -40,6 +40,12 @@ pub fn lazy_moved(cell: std::cell::Cell) -> u32 { *copy } +/// A pointer from a reference the body is given moved by an offset the body doesn't fix, +/// which may reach past the value JS keeps in no array. +pub fn offset_reference(r: &u32, n: usize) -> u32 { + unsafe { *std::ptr::from_ref(r).add(n) } +} + /// A `Peekable` moved out through a pointer with a cell peeked, which only its runtime /// class holds. pub fn peeked_moved(cells: Vec>) -> u32 { diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 96c6f563c..80f0a63bc 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -6,6 +6,7 @@ roots = [ "alloc::bytes_as_values", "alloc::infallible_cell", "alloc::lazy_moved", + "alloc::offset_reference", "alloc::past_reference", "alloc::peeked_moved", "alloc::reinterpreted", diff --git a/ts/runtime/rs.ts b/ts/runtime/rs.ts index 60b74149a..439e28b6a 100644 --- a/ts/runtime/rs.ts +++ b/ts/runtime/rs.ts @@ -679,12 +679,16 @@ export function ptrByteSlice(p: Place, len: number, shared: boolean): By /** * `ptr::write` of `value` through a pointer to an object: `value` copied into it, one - * object deep, or moved by `$move`, so every pointer to it sees the write. + * object deep, or moved by `$move`, so every pointer to it sees the write. A field `value` + * leaves out, such as a `HashMap`'s capacity it never set, goes from `p` too. */ export function ptrWrite(p: T, value: T): void { if (ArrayBuffer.isView(p)) (p as unknown as Uint8Array).set(value as unknown as Uint8Array); else if (moved(value)) value.$move(p as T & Moved); - else Object.assign(p, value); + else { + if (!Array.isArray(p)) for (const key of Object.keys(p)) if (!Object.hasOwn(value, key)) delete (p as Record)[key]; + Object.assign(p, value); + } } /** diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 15b5f2296..363a1915b 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -29,9 +29,18 @@ use super::{AdtRepr, Translator, field_name, names}; use crate::program::{SymKind, Symbol}; use crate::ts::{self, Expr, Key, Param, Stmt, StmtKind}; -/// What a library object holds inline: each field of its JS object holding a value and -/// the type held there, and the types it holds where no copy reaches. -type Inline<'tcx> = (Vec<(&'static str, Ty<'tcx>)>, Vec>); +/// What a library object holds inline, which its copy one object deep would share. +#[derive(Default)] +struct Inline<'tcx> { + /// Each field of its JS object holding a value inline, and the type held there. + fields: Vec<(&'static str, Ty<'tcx>)>, + /// Each field holding the JS array of its storage, whose length or slots the runtime + /// changes where Rust changes only the object's own length, as a `Vec`'s `set_len` and + /// a `VecDeque`'s `pop_front` do: copied, sharing its items. + storage: Vec<&'static str>, + /// The types it holds where no copy reaches, a hash map's hasher. + unreachable: Vec>, +} impl<'a, 'tcx> Translator<'a, 'tcx> { /// Whether a `ty` is copied where Rust copies it: a `Copy` value that is, or holds by @@ -71,10 +80,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { || self.addressed(ty) || !ty.is_freeze(self.tcx, self.typing_env()) || match self.inline_held(ty) { - Some((fields, unreachable)) => fields + Some(inline) => inline + .fields .into_iter() .map(|(_, t)| t) - .chain(unreachable) + .chain(inline.unreachable) .any(|t| self.changes(t)), // Any type it is over may be held inline, which its copy refuses. None => args.types().any(|t| self.changes(t)), @@ -95,16 +105,21 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// What a library object `ty` holds inline rather than on the heap, which a copy of the /// object one object deep shares: each field of its JS object holding such a value and /// the type held there, a cell's, a lock's, a range's ends, or a one-time cell's, which - /// holds nothing until set; and the types it holds where no copy reaches, a hash map's - /// hasher. A collection's items, and what a `Weak` points at, are on the heap, which - /// Rust's read shares too. `None` - /// for any other library object, such as a `LazyCell` or a `Peekable`, whose inline - /// state only its runtime class knows, so a read that would copy one is refused. + /// holds nothing until set; the JS arrays holding a collection's storage whose length + /// is its own; and the types it holds where no copy reaches, a hash map's hasher. A + /// collection's items, and what a `Weak` points at, are on the heap, which Rust's read + /// shares too. `None` for any other library object, such as a `LazyCell` or a + /// `Peekable`, whose inline state only its runtime class knows, so a read that would + /// copy one is refused. fn inline_held(&self, ty: Ty<'tcx>) -> Option> { let ty::Adt(adt, args) = ty.kind() else { return None; }; let at = |i: usize| args.type_at(i); + let storage = |field: &'static str| Inline { + storage: vec![field], + ..Inline::default() + }; let fields = match Std::of(self.tcx, adt.did()) { Some(Std::Cell) => vec![("v", at(0))], Some(Std::RefCell | Std::Mutex) => vec![("value", at(0))], @@ -112,10 +127,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some(Std::Range) => vec![("start", at(0)), ("end", at(0))], Some(Std::RangeInclusive) => vec![("lo", at(0)), ("hi", at(0))], Some(Std::RangeFrom) => vec![("start", at(0))], + // A `Vec`'s length is apart from its bytes. + Some(Std::Vec | Std::BinaryHeap) if !super::byte(at(0)) => return Some(storage("items")), + Some(Std::VecDeque) => return Some(storage("b")), Some( Std::Vec | Std::String - | Std::VecDeque | Std::LinkedList | Std::BinaryHeap | Std::BTreeMap @@ -124,11 +141,14 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::BytesMut | Std::Weak, ) => Vec::new(), - Some(Std::HashMap) => return Some((Vec::new(), vec![at(2)])), - Some(Std::HashSet) => return Some((Vec::new(), vec![at(1)])), + Some(Std::HashMap) => return Some(Inline { unreachable: vec![at(2)], ..Inline::default() }), + Some(Std::HashSet) => return Some(Inline { unreachable: vec![at(1)], ..Inline::default() }), _ => return None, }; - Some((fields, Vec::new())) + Some(Inline { + fields, + ..Inline::default() + }) } /// Whether the runs before compared pointers to a `ty`, or to a type it holds by value, @@ -402,7 +422,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { // A library object, which [`Self::changes`] says a whole write lands in, and a // copy of what it holds inline. Some(std) => { - let Some((fields, unreachable)) = self.inline_held(ty) else { + let Some(inline) = self.inline_held(ty) else { self.err( span, format!( @@ -411,7 +431,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ); return ret(v()); }; - if unreachable.into_iter().any(|t| self.changes(t)) { + if inline.unreachable.into_iter().any(|t| self.changes(t)) { self.err( span, format!("a read moving out a `{ty}`, which holds inline a value JS can't copy apart"), @@ -422,13 +442,17 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let held = self.snapshot(v().member("v"), args.type_at(0), span); return ret(Expr::New(Box::new(Expr::Rs("Cell".into())), vec![held])); } - let fields: Vec<_> = fields.into_iter().filter(|&(_, t)| self.changes(t)).collect(); + let fields: Vec<_> = inline.fields.into_iter().filter(|&(_, t)| self.changes(t)).collect(); let copy = Expr::rs("objectCopy", vec![v()]); - if fields.is_empty() { + if fields.is_empty() && inline.storage.is_empty() { return ret(copy); } let c = || Expr::ident("c"); let mut body = vec![StmtKind::decl(false, "c", None, Some(copy)).at(None)]; + for field in inline.storage { + let items = c().member(field).member("slice").call(Vec::new()); + body.push(StmtKind::Expr(c().member(field).assign(items)).at(None)); + } for (field, t) in fields { let read = self.snapshot(c().member(field), t, span); body.push(StmtKind::Expr(c().member(field).assign(read)).at(None)); diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index bce21d8f8..1472e2790 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -63,7 +63,8 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { /// Why the call of std's `def_id` would fail at run time where Rust's is defined, if the /// body proves it would: `offset_from` between pointers JS keeps in different arrays, a /// raw or mutable slice of a byte in no array of bytes, or a pointer, slice, or copy - /// reaching out of an array of one or a copied byte. + /// reaching out of an array of one or a copied byte, or moved by an offset the body + /// doesn't fix from an array of one. pub(super) fn stray( &self, def_id: rustc_hir::def_id::DefId, @@ -93,7 +94,15 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { return Some(super::alloc::BYTES.into()); } let out = |o: Origin<'tcx>, n: Option, what: &str| { - let index = o.index?; + // An offset the body doesn't fix may move a pointer out of an array of one. + let Some(index) = o.index else { + return matches!(o.array, Array::Lone(_)).then(|| { + format!( + "{what} moved by an offset the body doesn't fix from {}, which JS keeps apart from what holds it in Rust", + o.array.what() + ) + }); + }; let past = o.array.len().is_some_and(|len| index + n.unwrap_or(0) > len); (index < 0 || past).then(|| { format!( From 0b375b8b34690dbe88aa33b76cb9873b8ffb33d5 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 10:53:19 -0800 Subject: [PATCH 22/29] chore(ts): format, regenerate the conversions snapshot Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/conversions/expected/lib.ts | 4 +++- ts/fixtures/lang/pointers/src/pointers.rs | 5 ++++- ts/src/lower/copy.rs | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ts/fixtures/lang/conversions/expected/lib.ts b/ts/fixtures/lang/conversions/expected/lib.ts index a448b3248..c52db795d 100644 --- a/ts/fixtures/lang/conversions/expected/lib.ts +++ b/ts/fixtures/lang/conversions/expected/lib.ts @@ -10,7 +10,9 @@ export function copy$String(v: rs.String): rs.String { } export function copy$Vec_u32(v: rs.Vec): rs.Vec { - return rs.objectCopy(v); + const c = rs.objectCopy(v); + c.items = c.items.slice(); + return c; } export function copy$Vec_u8(v: rs.ByteVec): rs.ByteVec { diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index 039308d4b..c7a8cd4fb 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2767,7 +2767,10 @@ fn inline_moved(r: &RefCell>, o: &OnceCell>) -> (RefCell, mut q: std::collections::VecDeque) -> (Vec, std::collections::VecDeque, Option) { +fn shrunk_moved( + v: Vec, + mut q: std::collections::VecDeque, +) -> (Vec, std::collections::VecDeque, Option) { let mut v = mem::ManuallyDrop::new(v); let old_v = unsafe { ptr::read(&*v) }; unsafe { v.set_len(0) }; diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 363a1915b..30421f638 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -141,8 +141,18 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::BytesMut | Std::Weak, ) => Vec::new(), - Some(Std::HashMap) => return Some(Inline { unreachable: vec![at(2)], ..Inline::default() }), - Some(Std::HashSet) => return Some(Inline { unreachable: vec![at(1)], ..Inline::default() }), + Some(Std::HashMap) => { + return Some(Inline { + unreachable: vec![at(2)], + ..Inline::default() + }); + } + Some(Std::HashSet) => { + return Some(Inline { + unreachable: vec![at(1)], + ..Inline::default() + }); + } _ => return None, }; Some(Inline { From 742b2bd574e893efc7f4e26587b263697961e3b0 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 11:00:56 -0800 Subject: [PATCH 23/29] docs(ts): note storage copies and unfixed offsets from a lone place Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/README.md b/ts/README.md index 8d1748e23..551d6bc4f 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: 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` | `rs.Cow`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows, `rs.Cow>` holding the arrays of a slice of arrays; `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`, `Reverse`, `NonZero`, `AssertUnwindSafe` | 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`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | 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` 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`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, 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, copy, or write of bytes 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. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `Vec` one object deep, with what a cell, lock, or range holds inline copied too, and one with interior mutability, such as a `Cell`, always; any other library object such as a `LazyCell` or a `Peekable`, whose inline state only its runtime class knows, is refused where the read would copy it | +| `*const T`, `*mut T`, `NonNull` | 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` 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`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, 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, copy, or write of bytes the body shows reaching past either, a pointer it shows moved from a value in no array by an offset it doesn't fix, 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. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `Vec` one object deep, with what a cell, lock, or range holds inline, and the array a `Vec`'s or `VecDeque`'s items are in, copied too, and one with interior mutability, such as a `Cell`, always; any other library object such as a `LazyCell` or a `Peekable`, whose inline state only its runtime class knows, is refused where the read would copy it | | `MaybeUninit` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined`, as `Box::new_uninit` and each of `Box::new_uninit_slice`'s is; a slice's `write_clone_of_slice` clones into its slots without dropping what they held. `Box::new_zeroed`'s holds the value of zero bytes in each part they are one of, such as a number, `false`, `None`, a null pointer, or an enum's variant of a tag or niche of zero, and `undefined` in each part they are none of, such as a reference, for a write to fill before a read | | `ManuallyDrop` | `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 | From c77552afc0dce7c2f1ccc18562ed2318c45a8542 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 11:11:35 -0800 Subject: [PATCH 24/29] fix(ts): keep a place's accessors on a whole write, refuse unfixed wrapping offsets and copy counts from a lone place Co-Authored-By: Claude Opus 5.5 --- .../lang/pointers/expected/pointers.ts | 19 +++++++- ts/fixtures/lang/pointers/src/pointers.rs | 14 ++++++ ts/fixtures/reject/census.txt | 4 +- ts/fixtures/reject/expected.txt | 4 +- ts/fixtures/reject/src/alloc.rs | 13 ++++-- ts/fixtures/reject/ts.toml | 2 +- ts/runtime/rs.ts | 7 ++- ts/src/lower/mir/origin.rs | 43 ++++++++++++++----- 8 files changed, 85 insertions(+), 21 deletions(-) diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 3c2680840..8a13ee3ef 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -3615,6 +3615,14 @@ export function shrunkMoved(v: rs.Vec, q: rs.VecDeque): [rs.Vec< return [oldV, copy$collections_VecDeque_u32(q), rs.dequePopFront(q)] as [rs.Vec, rs.VecDeque, rs.u32 | undefined]; } +/** + * A cell written whole through a pointer from the shared reference given, which may be + * the cell `Cell::from_mut` makes of another place. + */ +export function cellRewritten(r: rs.Cell, n: rs.u32): void { + rs.placeIn(r).v = new rs.Cell(n); +} + /** * A `HashMap` written whole through a pointer from the `&mut` given, which holds what the * new one does, its capacity included. @@ -3643,8 +3651,8 @@ export function movedOut(data: rs.ByteSlice, out: rs.ByteVec): void { let _7!: rs.u8; let _28!: rs.u8; const with_: (a0: rs.u8) => rs.ByteVec = (n: rs.u8): rs.ByteVec => { - return rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b2: rs.u8): rs.u8 => { - return rs.u8WrappingAdd(b2, n); + return rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b3: rs.u8): rs.u8 => { + return rs.u8WrappingAdd(b3, n); }), undefined, true); }; let iterStart: rs.u8 = 0; @@ -3769,6 +3777,13 @@ export function movedOut(data: rs.ByteSlice, out: rs.ByteVec): void { rs.hashExtend(map, _232, undefined, undefined, undefined, 117440512); mapRewritten(map); rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(rs.hashLen(map) & 0xff, rs.hashCapacity(map) & 0xff)), undefined, true); + for (const b2 of rs.byteSliceView(data)) { + let x2: rs.u32 = b2 as rs.u32; + cellRewritten(rs.cellOfPlace(rs.place(() => x2, (value3) => x2 = value3)), rs.u32Add(b2 as rs.u32, 1)); + const cell2: rs.Cell = new rs.Cell(b2 as rs.u32); + cellRewritten(cell2, rs.u32Add(b2 as rs.u32, 2)); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(x2 & 0xff, cell2.v & 0xff)), undefined, true); + } } /** diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index c7a8cd4fb..4022201f2 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2780,6 +2780,13 @@ fn shrunk_moved( (old_v, old_q, popped) } +/// A cell written whole through a pointer from the shared reference given, which may be +/// the cell `Cell::from_mut` makes of another place. +#[inline(never)] +fn cell_rewritten(r: &Cell, n: u32) { + unsafe { ptr::from_ref(r).cast_mut().write(Cell::new(n)) } +} + /// A `HashMap` written whole through a pointer from the `&mut` given, which holds what the /// new one does, its capacity included. #[inline(never)] @@ -2870,6 +2877,13 @@ pub fn moved_out(data: &[u8], out: &mut Vec) { map.extend(nums.iter().map(|&n| (n, n))); map_rewritten(&mut map); out.extend([map.len() as u8, map.capacity() as u8]); + for &b in data { + let mut x = u32::from(b); + cell_rewritten(Cell::from_mut(&mut x), u32::from(b) + 1); + let cell = Cell::new(u32::from(b)); + cell_rewritten(&cell, u32::from(b) + 2); + out.extend([x as u8, cell.get() as u8]); + } } /// Pointers to slices cast to ones to arrays of their elements, of bytes and of numbers: diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 7c962d6f8..80bcdebea 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -12,10 +12,12 @@ fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart -fixtures/reject/src/alloc.rs:offset_reference+1:34: a pointer moved by an offset the body doesn't fix from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:peeked_moved+3:26: a read moving out a `std::iter::Peekable>>`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another +fixtures/reject/src/alloc.rs:unfixed_reference+3:35: a pointer moved by an offset the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:unfixed_reference+4:35: a pointer moved by an offset the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:unfixed_reference+5:3: a copy of a count the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:zeroed+1:12: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:13: unsupported call to `std::alloc::alloc_zeroed` fixtures/reject/src/buffers.rs:blank_bytes+1:2: `Buf::copy_to_bytes` of `buffers::Blank`, a value JS copies, which is not supported diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 001e9e243..1201c97a4 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -16,10 +16,12 @@ fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart -fixtures/reject/src/alloc.rs:offset_reference+1:34: a pointer moved by an offset the body doesn't fix from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:peeked_moved+3:26: a read moving out a `std::iter::Peekable>>`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:reinterpreted+1:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another +fixtures/reject/src/alloc.rs:unfixed_reference+3:35: a pointer moved by an offset the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:unfixed_reference+4:35: a pointer moved by an offset the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:unfixed_reference+5:3: a copy of a count the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:zeroed+1:12: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:zeroed+1:13: unsupported call to `std::alloc::alloc_zeroed` fixtures/reject/src/buffers.rs:blank_bytes+1:2: `Buf::copy_to_bytes` of `buffers::Blank`, a value JS copies, which is not supported diff --git a/ts/fixtures/reject/src/alloc.rs b/ts/fixtures/reject/src/alloc.rs index 98b108147..62f740cd6 100644 --- a/ts/fixtures/reject/src/alloc.rs +++ b/ts/fixtures/reject/src/alloc.rs @@ -41,9 +41,16 @@ pub fn lazy_moved(cell: std::cell::Cell) -> u32 { } /// A pointer from a reference the body is given moved by an offset the body doesn't fix, -/// which may reach past the value JS keeps in no array. -pub fn offset_reference(r: &u32, n: usize) -> u32 { - unsafe { *std::ptr::from_ref(r).add(n) } +/// wrapping or not, and copied from by a count it doesn't fix, each of which may reach +/// past the value JS keeps in no array. +pub fn unfixed_reference(r: &u32, n: usize) -> [u32; 3] { + let mut out = [0u32; 3]; + unsafe { + out[0] = *std::ptr::from_ref(r).add(n); + out[1] = *std::ptr::from_ref(r).wrapping_add(n); + std::ptr::copy_nonoverlapping(std::ptr::from_ref(r), out.as_mut_ptr(), n); + } + out } /// A `Peekable` moved out through a pointer with a cell peeked, which only its runtime diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 80f0a63bc..f0dfebb92 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -6,10 +6,10 @@ roots = [ "alloc::bytes_as_values", "alloc::infallible_cell", "alloc::lazy_moved", - "alloc::offset_reference", "alloc::past_reference", "alloc::peeked_moved", "alloc::reinterpreted", + "alloc::unfixed_reference", "alloc::zeroed", "bound_ranges", "buffers::blank_bytes", diff --git a/ts/runtime/rs.ts b/ts/runtime/rs.ts index 439e28b6a..258ca2fad 100644 --- a/ts/runtime/rs.ts +++ b/ts/runtime/rs.ts @@ -680,13 +680,16 @@ export function ptrByteSlice(p: Place, len: number, shared: boolean): By /** * `ptr::write` of `value` through a pointer to an object: `value` copied into it, one * object deep, or moved by `$move`, so every pointer to it sees the write. A field `value` - * leaves out, such as a `HashMap`'s capacity it never set, goes from `p` too. + * leaves out, such as a `HashMap`'s capacity it never set, goes from `p` too, unless `p` + * is of another class, such as the place `Cell::from_mut` makes, whose setters take it. */ export function ptrWrite(p: T, value: T): void { if (ArrayBuffer.isView(p)) (p as unknown as Uint8Array).set(value as unknown as Uint8Array); else if (moved(value)) value.$move(p as T & Moved); else { - if (!Array.isArray(p)) for (const key of Object.keys(p)) if (!Object.hasOwn(value, key)) delete (p as Record)[key]; + if (!Array.isArray(p) && Object.getPrototypeOf(p) === Object.getPrototypeOf(value)) { + for (const key of Object.keys(p)) if (!Object.hasOwn(value, key)) delete (p as Record)[key]; + } Object.assign(p, value); } } diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 1472e2790..1d15ee9e1 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -93,15 +93,18 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { { return Some(super::alloc::BYTES.into()); } + // An offset or a count the body doesn't fix may reach out of an array of one. + let unfixed = |o: Origin<'tcx>, what: &str| { + matches!(o.array, Array::Lone(_)).then(|| { + format!( + "{what} the body doesn't fix, from {}, which JS keeps apart from what holds it in Rust", + o.array.what() + ) + }) + }; let out = |o: Origin<'tcx>, n: Option, what: &str| { - // An offset the body doesn't fix may move a pointer out of an array of one. let Some(index) = o.index else { - return matches!(o.array, Array::Lone(_)).then(|| { - format!( - "{what} moved by an offset the body doesn't fix from {}, which JS keeps apart from what holds it in Rust", - o.array.what() - ) - }); + return unfixed(o, &format!("{what} moved by an offset")); }; let past = o.array.len().is_some_and(|len| index + n.unwrap_or(0) > len); (index < 0 || past).then(|| { @@ -139,6 +142,14 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { let o = self.moved(self.origin(arg(0)?)?, name.as_str(), arg(1)?); out(o, None, "a pointer") } + // A wrapping offset may leave the array while nothing reads through it. + ("ptr" | "NonNull", "wrapping_add" | "wrapping_sub" | "wrapping_offset") => { + let o = self.origin(arg(0)?)?; + match self.const_int(arg(1)?) { + Some(_) => None, + None => unfixed(o, "a pointer moved by an offset"), + } + } ("raw", "from_raw_parts" | "from_raw_parts_mut") | ("ptr", "slice_from_raw_parts" | "slice_from_raw_parts_mut") | ("NonNull", "slice_from_raw_parts") => { @@ -165,11 +176,21 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { ) | ("NonNull", "copy_to" | "copy_to_nonoverlapping" | "copy_from" | "copy_from_nonoverlapping") => { let n = self.const_int(arg(2)?); - [arg(0)?, arg(1)?] - .into_iter() - .find_map(|p| out(self.origin(p)?, n, "a copy")) + [arg(0)?, arg(1)?].into_iter().find_map(|p| { + let o = self.origin(p)?; + match n { + Some(_) => out(o, n, "a copy"), + None => unfixed(o, "a copy of a count"), + } + }) + } + ("ptr" | "NonNull", "write_bytes") => { + let o = self.origin(arg(0)?)?; + match self.const_int(arg(2)?) { + Some(n) => out(o, Some(n), "a write"), + None => unfixed(o, "a write of a count"), + } } - ("ptr" | "NonNull", "write_bytes") => out(self.origin(arg(0)?)?, self.const_int(arg(2)?), "a write"), _ => None, } } From e2a5e8cb9034dee8e2a9e3365452f714fa78fbc2 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 12:18:11 -0800 Subject: [PATCH 25/29] feat(ts): refuse unsplit collection moves and offsets off a lone place A move out through a pointer (`ptr::read`, `replace`, `swap`, `copy`, `assume_init_read`, `ManuallyDrop::take`) of a collection whose JS object keeps its length with its storage (a non-byte `Vec`, `VecDeque`, `String`, map, set, or list) is refused with a span, as no JS copy splits from the original as Rust's does. The storage copy in the shim is gone; safe moves keep the shallow copy, which is exact there. From an array of one (a pointer from a reference with no visible origin), every offset, wrapping or not, must stay at its element, and every count or slice length must be fixed and stay within it, or it is refused. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 2 +- .../lang/pointers/expected/core/option.ts | 14 - ts/fixtures/lang/pointers/expected/lib.ts | 24 +- .../pointers/expected/pointers.Drained.ts | 16 +- .../lang/pointers/expected/pointers.List.ts | 88 +++--- .../lang/pointers/expected/pointers.Node.ts | 8 +- .../lang/pointers/expected/pointers.ts | 257 ++++++------------ ts/fixtures/lang/pointers/src/pointers.rs | 47 +--- ts/fixtures/reject/census.txt | 8 + ts/fixtures/reject/expected.txt | 8 + ts/fixtures/reject/src/alloc.rs | 33 +++ ts/fixtures/reject/ts.toml | 2 + ts/src/lower/copy.rs | 76 +++--- ts/src/lower/mir/origin.rs | 34 ++- ts/src/lower/pointer.rs | 29 +- 15 files changed, 287 insertions(+), 359 deletions(-) diff --git a/ts/README.md b/ts/README.md index 38930c6a0..2d0ee4e9f 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: 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` | `rs.Cow`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows, `rs.Cow>` holding the arrays of a slice of arrays; `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`, `Reverse`, `NonZero`, `AssertUnwindSafe` | 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`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | 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` 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`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust, but a byte variable whose `&raw` the body takes, or that of a reference to it made there, as `&mut b as *mut u8` does, is element 0 of a `Uint8Array` of its own, which its raw, mutable, and shared slices view as they do any other bytes; JS keeps an array of one, and a shared subslice of a byte in no array, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, a pointer it shows moved from a value in no array by an offset, or a copy or write of a count, it doesn't fix, and `offset_from` between pointers the body shows JS keeps in different arrays, are refused, and through a pointer from elsewhere any of these, a raw or mutable slice of a byte in no array, such as a field's, 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. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `Vec` one object deep, with what a cell, lock, or range holds inline, and the array a `Vec`'s or `VecDeque`'s items are in, copied too, and one with interior mutability, such as a `Cell`, always; any other library object such as a `LazyCell` or a `Peekable`, whose inline state only its runtime class knows, is refused where the read would copy it | +| `*const T`, `*mut T`, `NonNull` | 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` 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`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust, but a byte variable whose `&raw` the body takes, or that of a reference to it made there, as `&mut b as *mut u8` does, is element 0 of a `Uint8Array` of its own, which its raw, mutable, and shared slices view as they do any other bytes; JS keeps an array of one, and a shared subslice of a byte in no array, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, a pointer it shows moved from a value in no array off it, or by an offset, or a copy, write, or slice of a count or length, it doesn't fix, and `offset_from` between pointers the body shows JS keeps in different arrays, are refused, and through a pointer from elsewhere any of these, a raw or mutable slice of a byte in no array, such as a field's, 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. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `RefCell` or a `Vec` one object deep, with what a cell, lock, or range holds inline, and one with interior mutability, such as a `Cell`, always; a collection whose JS object keeps its length with its storage, such as a `Vec` of other than bytes, a `VecDeque`, a `String`, a map, or a set, which no copy splits from the original as Rust's read does, and any other library object such as a `LazyCell` or a `Peekable`, whose inline state only its runtime class knows, are refused where the read would copy them | | `MaybeUninit` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined`, as `Box::new_uninit` and each of `Box::new_uninit_slice`'s is; a slice's `write_clone_of_slice` clones into its slots without dropping what they held. `Box::new_zeroed`'s holds the value of zero bytes in each part they are one of, such as a number, `false`, `None`, a null pointer, or an enum's variant of a tag or niche of zero, and `undefined` in each part they are none of, such as a reference, for a write to fill before a read | | `ManuallyDrop` | `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 | diff --git a/ts/fixtures/lang/pointers/expected/core/option.ts b/ts/fixtures/lang/pointers/expected/core/option.ts index 9ac98ea05..fec4d68cd 100644 --- a/ts/fixtures/lang/pointers/expected/core/option.ts +++ b/ts/fixtures/lang/pointers/expected/core/option.ts @@ -55,13 +55,6 @@ export function map$00buaky(self: rs.u16 | undefined | rs.None, f: (a0: rs.u16 | return f(self); } -export function map$1lzqy5c(self: rs.String | undefined, f: (a0: rs.String) => rs.String): rs.String | undefined { - if (self === undefined) { - return undefined; - } - return f(self); -} - export function map$1h9tk16(self: rs.ByteVec | undefined, f: (a0: rs.ByteVec) => rs.ByteVec): rs.ByteVec | undefined { if (self === undefined) { return undefined; @@ -139,13 +132,6 @@ export function mapOr$0smr0aj(self: rs.u16 | undefined, default_: Uint8Array, _: return rs.u16ToBeBytes(self); } -export function mapOr$07pvkk0(self: rs.u32 | undefined, default_: rs.u8, f: (a0: rs.u32) => rs.u8): rs.u8 { - if (self === undefined) { - return default_; - } - return f(self); -} - export function filter$0wvwd70(self: rs.u32 | undefined, predicate: (a0: rs.u32) => boolean): rs.u32 | undefined { if (self !== undefined) { const x: rs.u32 = self; diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index ef94d0827..564155089 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -41,16 +41,6 @@ export function copy$Option_tuple2_u16_u8(v: [rs.u16, rs.u8] | undefined): [rs.u return v === undefined ? v : copy$tuple2_u16_u8(v); } -export function copy$String(v: rs.String): rs.String { - return rs.objectCopy(v); -} - -export function copy$Vec_u32(v: rs.Vec): rs.Vec { - const c = rs.objectCopy(v); - c.items = c.items.slice(); - return c; -} - export function copy$Vec_u8(v: rs.ByteVec): rs.ByteVec { return rs.objectCopy(v); } @@ -91,12 +81,6 @@ export function copy$cell_RefCell_Cell_u32(v: rs.RefCell>): rs.R return c; } -export function copy$collections_VecDeque_u32(v: rs.VecDeque): rs.VecDeque { - const c = rs.objectCopy(v); - c.b = c.b.slice(); - return c; -} - export function copy$mem_MaybeUninit_Vec_u8(v: rs.ByteVec): rs.ByteVec { if (v === undefined) { return v; @@ -260,8 +244,8 @@ export function copy$tuple2_u32_u32(v: [rs.u32, rs.u32]): [rs.u32, rs.u32] { return [v[0], v[1]]; } -export function copy$tuple2_u8_String(v: [rs.u8, rs.String]): [rs.u8, rs.String] { - return [v[0], copy$String(v[1])]; +export function copy$tuple2_u8_Vec_u8(v: [rs.u8, rs.ByteVec]): [rs.u8, rs.ByteVec] { + return [v[0], copy$Vec_u8(v[1])]; } export function copy$tuple2_u8_sync_Arc_u8(v: [rs.u8, rs.Arc]): [rs.u8, rs.Arc] { @@ -272,8 +256,8 @@ export function copy$tuple2_u8_u8(v: [rs.u8, rs.u8]): [rs.u8, rs.u8] { return [v[0], v[1]]; } -export function drop$pointers_List_String(_1: List.List): void { - List.drop$ops_Drop_String(_1); +export function drop$pointers_List_Vec_u8(_1: List.List): void { + List.drop$ops_Drop_Vec_u8(_1); } export function drop$pointers_List_sync_Arc_u8(_1: List.List>): void { diff --git a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts index 5e2a38872..36f4394f1 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.Drained.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.Drained.ts @@ -1,8 +1,8 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$String, copy$Vec_u8, copy$sync_Weak_u32 } from "./lib.ts"; -import { map$00buaky as option_map$00buaky, map$1lzqy5c as option_map$1lzqy5c, map$1h9tk16 as option_map$1h9tk16, map$1ng7x7l as option_map$1ng7x7l, map$070fw7m as option_map$070fw7m } from "./core/option.ts"; +import { copy$Vec_u8, copy$sync_Weak_u32 } from "./lib.ts"; +import { map$00buaky as option_map$00buaky, map$1h9tk16 as option_map$1h9tk16, map$1ng7x7l as option_map$1ng7x7l, map$070fw7m as option_map$070fw7m } from "./core/option.ts"; /** * What smallvec's `Drain` is: the elements of a slice nothing will drop again, each read @@ -18,12 +18,6 @@ export function next$iter_Iterator_Option_u16(self: Drained) }), rs.None); } -export function next$iter_Iterator_String(self: Drained): rs.String | rs.Done { - return rs.iterFromOption(option_map$1lzqy5c(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.String): rs.String => { - return copy$String(r); - })); -} - export function next$iter_Iterator_Vec_u8(self: Drained): rs.ByteVec | rs.Done { return rs.iterFromOption(option_map$1h9tk16(rs.iterToOption((self.iter as rs.Iter).next()), (r: rs.ByteVec): rs.ByteVec => { return copy$Vec_u8(r); @@ -48,12 +42,6 @@ export function nextBack$iter_DoubleEndedIterator_Option_u16(self: Drained): rs.String | undefined { - return option_map$1lzqy5c(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.String): rs.String => { - return copy$String(r); - }); -} - export function nextBack$iter_DoubleEndedIterator_Vec_u8(self: Drained): rs.ByteVec | undefined { return option_map$1h9tk16(rs.iterToOption((self.iter as rs.Iter).nextBack()), (r: rs.ByteVec): rs.ByteVec => { return copy$Vec_u8(r); diff --git a/ts/fixtures/lang/pointers/expected/pointers.List.ts b/ts/fixtures/lang/pointers/expected/pointers.List.ts index b812f3f11..4bbebb0fb 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.List.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.List.ts @@ -1,8 +1,8 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$tuple2_u8_String, copy$tuple2_u8_sync_Arc_u8, drop$tuple2_u8_sync_Arc_u8 } from "./lib.ts"; -import { attachBefore$String, attachBefore$sync_Arc_u8, detach$String, detach$sync_Arc_u8 } from "./pointers.ts"; +import { copy$tuple2_u8_Vec_u8, copy$tuple2_u8_sync_Arc_u8, drop$tuple2_u8_sync_Arc_u8 } from "./lib.ts"; +import { attachBefore$Vec_u8, attachBefore$sync_Arc_u8, detach$Vec_u8, detach$sync_Arc_u8 } from "./pointers.ts"; import type * as Free from "./pointers.Free.ts"; import type * as Links from "./pointers.Links.ts"; import * as Node from "./pointers.Node.ts"; @@ -21,7 +21,7 @@ export class List { } } -export function new$String(): List { +export function new$Vec_u8(): List { return new List(undefined, undefined, 0); } @@ -29,13 +29,13 @@ export function new$sync_Arc_u8(): List> { return new List(undefined, undefined, 0); } -export function guard$String(self: List): Node.Node { +export function guard$Vec_u8(self: List): Node.Node { if (self.guard !== undefined) { return self.guard; } - const guard2: Node.Node = Node.new$String(); - const _8: Links.Links = { next: guard2, prev: guard2 }; - const _11: Node.Node = guard2; + const guard2: Node.Node = Node.new$Vec_u8(); + const _8: Links.Links = { next: guard2, prev: guard2 }; + const _11: Node.Node = guard2; _11.link.at0 = _8.next; _11.link.at4 = _8.prev; self.guard = guard2; @@ -55,11 +55,11 @@ export function guard$sync_Arc_u8(self: List>): Node.Node): Node.Node { +export function alloc$Vec_u8(self: List): Node.Node { if (self.free === undefined) { - return Node.new$String(); + return Node.new$Vec_u8(); } - const free: Node.Node = self.free; + const free: Node.Node = self.free; self.free = free.link.at0; return free; } @@ -73,11 +73,11 @@ export function alloc$sync_Arc_u8(self: List>): Node.Node, key: rs.u8, value: rs.String): void { - const guard: Node.Node = guard$String(self); - const node: Node.Node = alloc$String(self); - rs.ptrWrite(node.entry, [key, value] as [rs.u8, rs.String]); - attachBefore$String(node, guard); +export function pushBack$Vec_u8(self: List, key: rs.u8, value: rs.ByteVec): void { + const guard: Node.Node = guard$Vec_u8(self); + const node: Node.Node = alloc$Vec_u8(self); + rs.ptrWrite(node.entry, [key, value] as [rs.u8, rs.ByteVec]); + attachBefore$Vec_u8(node, guard); self.len = rs.usizeAdd(self.len, 1); } @@ -89,14 +89,14 @@ export function pushBack$sync_Arc_u8(self: List>, key: rs.u8, valu self.len = rs.usizeAdd(self.len, 1); } -export function find$String(self: List, key: rs.u8): Node.Node | undefined { - const _4: Node.Node | undefined = self.guard; +export function find$Vec_u8(self: List, key: rs.u8): Node.Node | undefined { + const _4: Node.Node | undefined = self.guard; if (_4 !== undefined) { - const guard: Node.Node = _4; - let at: Node.Node = guard.link.at0 as Node.Node; + const guard: Node.Node = _4; + let at: Node.Node = guard.link.at0 as Node.Node; while (at !== guard) { if (!(at.entry[0] === key)) { - at = at.link.at0 as Node.Node; + at = at.link.at0 as Node.Node; } else { return at; } @@ -123,13 +123,13 @@ export function find$sync_Arc_u8(self: List>, key: rs.u8): Node.No return undefined; } -export function remove$String(self: List, key: rs.u8): [rs.u8, rs.String] | undefined { - const _4: Node.Node | undefined = find$String(self, key); +export function remove$Vec_u8(self: List, key: rs.u8): [rs.u8, rs.ByteVec] | undefined { + const _4: Node.Node | undefined = find$Vec_u8(self, key); if (_4 !== undefined) { - const node: Node.Node = _4; - detach$String(node); - const entry: [rs.u8, rs.String] = copy$tuple2_u8_String(node.entry); - const _16: Free.Free = { next: self.free }; + const node: Node.Node = _4; + detach$Vec_u8(node); + const entry: [rs.u8, rs.ByteVec] = copy$tuple2_u8_Vec_u8(node.entry); + const _16: Free.Free = { next: self.free }; node.link.at0 = _16.next; self.free = node; self.len = rs.usizeSub(self.len, 1); @@ -153,14 +153,14 @@ export function remove$sync_Arc_u8(self: List>, key: rs.u8): [rs.u return undefined; } -export function toFront$String(self: List, key: rs.u8): boolean { - const _4: Node.Node | undefined = find$String(self, key); +export function toFront$Vec_u8(self: List, key: rs.u8): boolean { + const _4: Node.Node | undefined = find$Vec_u8(self, key); if (_4 !== undefined) { - const node: Node.Node = _4; + const node: Node.Node = _4; if (self.guard !== undefined) { - const guard: Node.Node = self.guard; - detach$String(node); - attachBefore$String(node, guard.link.at0 as Node.Node); + const guard: Node.Node = self.guard; + detach$Vec_u8(node); + attachBefore$Vec_u8(node, guard.link.at0 as Node.Node); return true; } return false; @@ -183,38 +183,38 @@ export function toFront$sync_Arc_u8(self: List>, key: rs.u8): bool return false; } -export function getMut$String(self: List, key: rs.u8): rs.String | undefined { - const _3: Node.Node | undefined = find$String(self, key); +export function getMut$Vec_u8(self: List, key: rs.u8): rs.ByteVec | undefined { + const _3: Node.Node | undefined = find$Vec_u8(self, key); if (_3 !== undefined) { return _3.entry[1]; } return undefined; } -export function each$String(self: List, out: rs.ByteVec): void { +export function each$Vec_u8(self: List, out: rs.ByteVec): void { b0: { if (self.guard !== undefined) { - const guard: Node.Node = self.guard; - let at: Node.Node = guard.link.at0 as Node.Node; + const guard: Node.Node = self.guard; + let at: Node.Node = guard.link.at0 as Node.Node; while (true) { if (!(at !== guard)) { break b0; } - const _13: [rs.u8, rs.String] = at.entry; + const _13: [rs.u8, rs.ByteVec] = at.entry; rs.byteVecPush(out, _13[0]); - rs.byteVecExtendFromSlice(out, rs.stringAsBytes(_13[1].v)); - at = at.link.at0 as Node.Node; + rs.byteVecExtendFromSlice(out, rs.byteVecSlice(_13[1])); + at = at.link.at0 as Node.Node; } } } } -export function drop$ops_Drop_String(self: List): void { +export function drop$ops_Drop_Vec_u8(self: List): void { if (self.guard !== undefined) { - const guard: Node.Node = self.guard; - let at: Node.Node = guard.link.at0 as Node.Node; + const guard: Node.Node = self.guard; + let at: Node.Node = guard.link.at0 as Node.Node; while (at !== guard) { - at = at.link.at0 as Node.Node; + at = at.link.at0 as Node.Node; } } while (self.free !== undefined) { diff --git a/ts/fixtures/lang/pointers/expected/pointers.Node.ts b/ts/fixtures/lang/pointers/expected/pointers.Node.ts index bcee03554..df628a145 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.Node.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.Node.ts @@ -9,10 +9,10 @@ export interface Node { link: Link.Link; } -export function new$String(): Node { - const _1: [rs.u8, rs.String] = ([undefined, undefined] as unknown) as [rs.u8, rs.String]; - const _3: Links.Links = { next: rs.DANGLING, prev: rs.DANGLING }; - return { entry: _1, link: ({ at0: _3.next, at4: _3.prev } as unknown) as Link.Link }; +export function new$Vec_u8(): Node { + const _1: [rs.u8, rs.ByteVec] = ([undefined, undefined] as unknown) as [rs.u8, rs.ByteVec]; + const _3: Links.Links = { next: rs.DANGLING, prev: rs.DANGLING }; + return { entry: _1, link: ({ at0: _3.next, at4: _3.prev } as unknown) as Link.Link }; } export function new$sync_Arc_u8(): Node> { diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 605b7e2aa..912806b0d 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -1,9 +1,9 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$Cell_u32, copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$String, copy$Vec_u32, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$cell_OnceCell_Cell_u32, copy$cell_RefCell_Cell_u32, copy$collections_VecDeque_u32, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$ops_Range_Cell_u32, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$sync_Weak_u32, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_String, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; +import { copy$Cell_u32, copy$Option_Cell_u32, copy$Option_Vec_u8, copy$Option_pointers_Pair, copy$Option_tuple2_u16_u8, copy$Vec_u8, copy$array_array2_u32, copy$array_ptr_array4_u8, copy$array_tuple2_u8_u8, copy$array_u16, copy$array_u32, copy$array_u8, copy$cell_OnceCell_Cell_u32, copy$cell_RefCell_Cell_u32, copy$mem_MaybeUninit_Vec_u8, copy$mem_MaybeUninit_array_u32, copy$mem_MaybeUninit_array_u8, copy$mem_MaybeUninit_tuple2_u8_u8, copy$ops_Range_Cell_u32, copy$pointers_Aimed_Cell_u8, copy$pointers_Aimed_array2_u16, copy$pointers_Aimed_array4_u8, copy$pointers_Aimed_pointers_Housed_u8, copy$pointers_Aimed_pointers_Leaning, copy$pointers_Aimed_pointers_Paired, copy$pointers_Aimed_u8, copy$pointers_ByCall, copy$pointers_ByClosure, copy$pointers_ByObject, copy$pointers_Duo, copy$pointers_Either_u32, copy$pointers_Either_u8, copy$pointers_Halves, copy$pointers_Outer, copy$pointers_Pair, copy$pointers_Spot, copy$pointers_TaggedVec, copy$pointers_Twin, copy$sync_Weak_u32, copy$tuple2_Vec_u8_u8, copy$tuple2_array2_u32_u8, copy$tuple2_u16_u8, copy$tuple2_u32_u32, copy$tuple2_u8_u8, drop$pointers_List_Vec_u8, drop$pointers_List_sync_Arc_u8, drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin, drop$tuple3_sync_Arc_u32_Rc_pointers_Duo_sync_Arc_unit, drop$tuple4_sync_Arc_pointers_Twin_sync_Arc_pointers_Twin_Rc_pointers_Twin_Rc_pointers_Twin, of$pointers_Bin_pointers_ByObject } from "./lib.ts"; import { thenSome$1gvhopg, then$01qpu7s, then$0bgt67i, then$1a5xgoh, then$0jw3xip } from "./core/bool.ts"; -import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, map$1px2rk4 as option_map$1px2rk4, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$18adzvj as option_mapOr$18adzvj, mapOr$0emjeap as option_mapOr$0emjeap, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, mapOr$07pvkk0 as option_mapOr$07pvkk0, filter$0wvwd70 } from "./core/option.ts"; +import { isSomeAnd$1s2u5lz, isSomeAnd$1nosv3e, isSomeAnd$07atiew, unwrapOr$0mkoact as option_unwrapOr$0mkoact, unwrapOr$179xo1w as option_unwrapOr$179xo1w, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1cfrse5 as option_map$1cfrse5, map$1px2rk4 as option_map$1px2rk4, mapOr$078vnqn as option_mapOr$078vnqn, mapOr$00zno7m as option_mapOr$00zno7m, mapOr$18adzvj as option_mapOr$18adzvj, mapOr$0emjeap as option_mapOr$0emjeap, mapOr$173j2gr as option_mapOr$173j2gr, mapOr$0smr0aj as option_mapOr$0smr0aj, filter$0wvwd70 } from "./core/option.ts"; import type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; import type * as ByCall from "./pointers.ByCall.ts"; @@ -53,13 +53,13 @@ import * as Wrapped from "./pointers.Wrapped.ts"; import type * as Written from "./pointers.Written.ts"; /** Put `node` in the list just before `at`. */ -export function attachBefore$String(node: Node.Node, at: Node.Node): void { - const _3: Links.Links = { next: at, prev: at.link.at4 }; - const _8: Node.Node = node; +export function attachBefore$Vec_u8(node: Node.Node, at: Node.Node): void { + const _3: Links.Links = { next: at, prev: at.link.at4 }; + const _8: Node.Node = node; _8.link.at0 = _3.next; _8.link.at4 = _3.prev; at.link.at4 = node; - (node.link.at4.link.at0 as Node.Node) = node; + (node.link.at4.link.at0 as Node.Node) = node; } /** Put `node` in the list just before `at`. */ @@ -72,9 +72,9 @@ export function attachBefore$sync_Arc_u8(node: Node.Node>, at: Nod (node.link.at4.link.at0 as Node.Node>) = node; } -export function detach$String(node: Node.Node): void { - (node.link.at4.link.at0 as Node.Node) = node.link.at0 as Node.Node; - (node.link.at0 as Node.Node).link.at4 = node.link.at4; +export function detach$Vec_u8(node: Node.Node): void { + (node.link.at4.link.at0 as Node.Node) = node.link.at0 as Node.Node; + (node.link.at0 as Node.Node).link.at4 = node.link.at4; } export function detach$sync_Arc_u8(node: Node.Node>): void { @@ -87,7 +87,7 @@ export function detach$sync_Arc_u8(node: Node.Node>): void { * finds and then the list in order. */ export function lists(dataData: Uint8Array, dataStart: number, dataLen: number, out: rs.ByteVec): void { - const list: List.List = List.new$String(); + const list: List.List = List.new$Vec_u8(); let dataData2: Uint8Array = dataData; let dataStart2: number = dataStart; let dataLen2: number = dataLen; @@ -105,36 +105,37 @@ export function lists(dataData: Uint8Array, dataStart: number, dataLen: number, const key2: rs.u8 = _13[1]; const _18: rs.u8 = rs.u8Rem(op2, 4); if (_18 === 0) { - const _26: rs.usize = rs.u8Rem(op2, 3) as rs.usize; - if (!(_26 < 3)) { - rs.panic("index out of bounds: the len is " + 3 + " but the index is " + _26); + const _22: rs.ByteSlice[] = [rs.byteSliceRange(rs.byteSliceFrom(Uint8Array.of()), 0), rs.byteSliceFrom(Uint8Array.of(118)), rs.byteSliceFrom(Uint8Array.of(118, 118))]; + const _29: rs.usize = rs.u8Rem(op2, 3) as rs.usize; + if (!(_29 < 3)) { + rs.panic("index out of bounds: the len is " + 3 + " but the index is " + _29); } - const _21: rs.String = rs.stringFrom(["", "v", "vv"][_26]); - List.pushBack$String(list, key2, _21); + const _21: rs.ByteVec = rs.byteSliceToByteVec(_22[_29]); + List.pushBack$Vec_u8(list, key2, _21); } else if (_18 === 1) { - const _30: [rs.u8, rs.String] | undefined = List.remove$String(list, key2); - if (_30 === undefined) { + const _33: [rs.u8, rs.ByteVec] | undefined = List.remove$Vec_u8(list, key2); + if (_33 === undefined) { rs.byteVecPush(out, 255); } else { - const k: rs.u8 = _30[0]; - const v: rs.String = _30[1]; + const k: rs.u8 = _33[0]; + const v: rs.ByteVec = _33[1]; rs.byteVecPush(out, k); - rs.byteVecExtendFromSlice(out, rs.stringAsBytes(v.v)); + rs.byteVecExtendFromSlice(out, rs.byteVecSlice(v)); } } else if (_18 === 2) { - rs.byteVecPush(out, List.toFront$String(list, key2) ? 1 : 0); + rs.byteVecPush(out, List.toFront$Vec_u8(list, key2) ? 1 : 0); } else { - const _42: rs.String | undefined = List.getMut$String(list, key2); - if (_42 === undefined) { + const _45: rs.ByteVec | undefined = List.getMut$Vec_u8(list, key2); + if (_45 === undefined) { rs.byteVecPush(out, 254); } else { - rs.stringPush(_42, 119); + rs.byteVecPush(_45, 119); } } } rs.byteVecPush(out, list.len & 0xff); - List.each$String(list, out); - drop$pointers_List_String(list); + List.each$Vec_u8(list, out); + drop$pointers_List_Vec_u8(list); } /** @@ -3009,31 +3010,6 @@ export function drained$Option_u16(v: rs.Vec, bits: rs.u8): } } -/** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ -export function drained$String(v: rs.Vec, bits: rs.u8): rs.Vec { - let next!: rs.String | undefined; - const v2: rs.Vec = v; - const iter: Drained.Drained = { iter: rs.iterSlice(v2.items) as rs.Iter> }; - const out: rs.Vec = new rs.Vec(); - let i: rs.i32 = 0; - while (true) { - if ((rs.u8Shr(bits, rs.i32Rem(i, 8)) & 1) === 0) { - next = rs.iterToOption(Drained.next$iter_Iterator_String(iter)); - } else { - next = Drained.nextBack$iter_DoubleEndedIterator_String(iter); - } - if (next !== undefined) { - out.items.push(next); - i = rs.i32Add(i, 1); - } else { - rs.vecSetLen(v2, 0, 12, (): rs.String => { - return (undefined as unknown) as rs.String; - }); - return out; - } - } -} - /** `v`'s elements, read out from both ends as `bits` picks, which nothing drops in `v`. */ export function drained$Vec_u8(v: rs.Vec, bits: rs.u8): rs.Vec { let next!: rs.ByteVec | undefined; @@ -3115,7 +3091,7 @@ export function drained$u32(v: rs.Vec, bits: rs.u8): rs.Vec { * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then * drops or moves to the front. */ -export function retained$Rc_pointers_Counted_pointers_behind_refs_closure10(v: rs.Vec>, keep: (a0: rs.Place>) => boolean): void { +export function retained$Rc_pointers_Counted_pointers_behind_refs_closure7(v: rs.Vec>, keep: (a0: rs.Place>) => boolean): void { let _10!: rs.usize; const len: rs.usize = v.items.length; const p: rs.Place> = rs.vecAsPtr(v, 4); @@ -3140,30 +3116,7 @@ export function retained$Rc_pointers_Counted_pointers_behind_refs_closure10(v: r * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then * drops or moves to the front. */ -export function retained$String_pointers_behind_refs_closure6(v: rs.Vec, keep: (a0: rs.String) => boolean): void { - let _10!: rs.usize; - const len: rs.usize = v.items.length; - const p: rs.Place = rs.vecAsPtr(v, 12); - let write: rs.usize = 0; - let iterStart: rs.usize = 0; - const iterEnd: rs.usize = len; - while ((_10 = iterStart++) < iterEnd) { - const _15: rs.Place = rs.ptrAdd(p, _10); - if (keep(_15.v)) { - rs.ptrCopy(_15, rs.ptrAdd(p, write), 1, copy$String); - write = rs.usizeAdd(write, 1); - } - } - rs.vecSetLen(v, write, 12, (): rs.String => { - return (undefined as unknown) as rs.String; - }); -} - -/** - * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then - * drops or moves to the front. - */ -export function retained$Vec_u8_pointers_behind_refs_closure8(v: rs.Vec, keep: (a0: rs.ByteVec) => boolean): void { +export function retained$Vec_u8_pointers_behind_refs_closure5(v: rs.Vec, keep: (a0: rs.ByteVec) => boolean): void { let _10!: rs.usize; const len: rs.usize = v.items.length; const p: rs.Place = rs.vecAsPtr(v, 12); @@ -3186,7 +3139,7 @@ export function retained$Vec_u8_pointers_behind_refs_closure8(v: rs.Vec>, keep: (a0: rs.Weak) => boolean): void { +export function retained$sync_Weak_u32_pointers_behind_refs_closure11(v: rs.Vec>, keep: (a0: rs.Weak) => boolean): void { let _10!: rs.usize; const len: rs.usize = v.items.length; const p: rs.Place> = rs.vecAsPtr(v, 4); @@ -3211,7 +3164,7 @@ export function retained$sync_Weak_u32_pointers_behind_refs_closure14(v: rs.Vec< * What smallvec's `retain` is: each element lent as a `&mut`, which a pointer from it then * drops or moves to the front. */ -export function retained$u32_pointers_behind_refs_closure4(v: rs.Vec, keep: (a0: rs.Place) => boolean): void { +export function retained$u32_pointers_behind_refs_closure3(v: rs.Vec, keep: (a0: rs.Place) => boolean): void { let _10!: rs.usize; const len: rs.usize = v.items.length; const p: rs.Place = rs.vecAsPtr(v, 4); @@ -3331,8 +3284,8 @@ export function cellBehind(r: rs.Cell | undefined, how: rs.u8): rs.u32 { * from references to an `Arc`'s value, measured against each other. */ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { - let _175!: rs.usize; - let _215!: rs.u8; + let _151!: rs.usize; + let _191!: rs.u8; const bits: rs.u8 = option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0); const nums: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_2: rs.u8): rs.u32 => { return rs.u32Mul(_2 as rs.u32, 3); @@ -3340,49 +3293,33 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(drained$u32(rs.vecFrom(nums.items.slice()), bits).items), (_23: rs.u32): rs.u8 => { return _23 & 0xff; }), undefined, true); - const words: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b3: rs.u8): rs.String => { - return rs.fmtToString((f: rs.Formatter) => rs.u8Display(b3, f), 0); - }), undefined, 12, true); - for (const _25 of drained$String(rs.vecFrom(words.items.map((a0: rs.String): rs.String => { - return rs.stringClone(a0); - })), bits).items) { - rs.iterExtendBytes(out, rs.strBytes(_25.v), undefined, true); - } - const runs: rs.Vec = rs.iterToVec(rs.iterMap(rs.byteSliceChunks(data, 3), (a02: rs.ByteSlice): rs.ByteVec => { - return rs.byteSliceToByteVec(a02); + const runs: rs.Vec = rs.iterToVec(rs.iterMap(rs.byteSliceChunks(data, 3), (a0: rs.ByteSlice): rs.ByteVec => { + return rs.byteSliceToByteVec(a0); }), undefined, 12, true); - for (const r of drained$Vec_u8(rs.vecFrom(runs.items.map((a03: rs.ByteVec): rs.ByteVec => { - return rs.byteVecFrom(rs.byteVecView(a03)); + for (const r of drained$Vec_u8(rs.vecFrom(runs.items.map((a02: rs.ByteVec): rs.ByteVec => { + return rs.byteVecFrom(rs.byteVecView(a02)); })), bits).items) { rs.byteVecPush(out, r.length & 0xff); rs.iterExtendBytes(out, rs.byteVecIntoIter(r), undefined, true); } - for (const _56 of drained$Option_u16(rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_24: rs.u8): rs.u16 | undefined => { - const b4: rs.u8 = _24; - return thenSome$1gvhopg(rs.u8Rem(b4, 3) !== 0, b4 as rs.u16); + for (const _40 of drained$Option_u16(rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_24: rs.u8): rs.u16 | undefined => { + const b3: rs.u8 = _24; + return thenSome$1gvhopg(rs.u8Rem(b3, 3) !== 0, b3 as rs.u16); }), undefined, 4, true), bits).items) { - rs.iterExtendBytes(out, rs.iterSlice(option_mapOr$0smr0aj(_56, Uint8Array.of(255, 255), (a04: rs.u16): Uint8Array => { - return rs.u16ToBeBytes(a04); + rs.iterExtendBytes(out, rs.iterSlice(option_mapOr$0smr0aj(_40, Uint8Array.of(255, 255), (a03: rs.u16): Uint8Array => { + return rs.u16ToBeBytes(a03); })), undefined, true); } const nums2: rs.Vec = nums; - retained$u32_pointers_behind_refs_closure4(nums2, (n3: rs.Place): boolean => { + retained$u32_pointers_behind_refs_closure3(nums2, (n3: rs.Place): boolean => { n3.v = rs.u32Add(n3.v, 1); return rs.u32Rem(n3.v, 2) === 0; }); rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(nums2.items), (_26: rs.u32): rs.u8 => { return _26 & 0xff; }), undefined, true); - const words2: rs.Vec = words; - retained$String_pointers_behind_refs_closure6(words2, (w4: rs.String): boolean => { - rs.stringPush(w4, 33); - return rs.usizeRem(w4.len, 2) === 0; - }); - rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(words2.items), (w5: rs.String): rs.Iter => { - return rs.strBytes(w5.v); - }, undefined, true), "next"); const runs2: rs.Vec = runs; - retained$Vec_u8_pointers_behind_refs_closure8(runs2, (r2: rs.ByteVec): boolean => { + retained$Vec_u8_pointers_behind_refs_closure5(runs2, (r2: rs.ByteVec): boolean => { rs.byteVecPush(r2, 7); return rs.u8Rem(r2.data[rs.idx(r2, 0)], 2) === 0; }); @@ -3391,7 +3328,7 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { const counted: rs.Vec> = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_27: rs.u8): rs.Arc => { return new rs.Arc(new Counted.Counted(_27, dropped)); }), undefined, 4, true); - retained$Rc_pointers_Counted_pointers_behind_refs_closure10(counted, (c: rs.Place>): boolean => { + retained$Rc_pointers_Counted_pointers_behind_refs_closure7(counted, (c: rs.Place>): boolean => { return rs.u8Rem(c.v.value.id, 2) === 0; }); rs.byteVecPush(out, dropped.v); @@ -3407,32 +3344,32 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { } return new rs.Weak(); }), undefined, 4, true); - for (const w2 of drained$sync_Weak_u32(rs.vecFrom(weaks.items.map((a05: rs.Weak): rs.Weak => { - return rs.weakClone(a05); + for (const w of drained$sync_Weak_u32(rs.vecFrom(weaks.items.map((a04: rs.Weak): rs.Weak => { + return rs.weakClone(a04); })), bits).items) { - rs.byteVecPush(out, option_mapOr$0emjeap(rs.weakUpgrade(w2), 255, (n5: rs.Arc): rs.u8 => { - const _019: rs.u8 = n5.value & 0xff; + rs.byteVecPush(out, option_mapOr$0emjeap(rs.weakUpgrade(w), 255, (n5: rs.Arc): rs.u8 => { + const _015: rs.u8 = n5.value & 0xff; n5[Symbol.dispose](); - return _019; + return _015; })); - w2[Symbol.dispose](); + w[Symbol.dispose](); } const weaks2: rs.Vec> = weaks; let i: rs.u8 = 0; - retained$sync_Weak_u32_pointers_behind_refs_closure14(weaks2, (w6: rs.Weak): boolean => { + retained$sync_Weak_u32_pointers_behind_refs_closure11(weaks2, (w3: rs.Weak): boolean => { i = rs.u8Add(i, 1); if (rs.u8Rem(i, 3) === 0) { - const _86: rs.Weak = new rs.Weak(arc); - w6[Symbol.dispose](); - Object.assign(w6, _86); + const _84: rs.Weak = new rs.Weak(arc); + w3[Symbol.dispose](); + Object.assign(w3, _84); } return rs.u8Rem(i, 2) === 0; }); - rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(weaks2.items), (w7: rs.Weak): rs.u8 => { - return option_mapOr$0emjeap(rs.weakUpgrade(w7), 255, (n6: rs.Arc): rs.u8 => { - const _022: rs.u8 = n6.value & 0xff; + rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(weaks2.items), (w4: rs.Weak): rs.u8 => { + return option_mapOr$0emjeap(rs.weakUpgrade(w4), 255, (n6: rs.Arc): rs.u8 => { + const _018: rs.u8 = n6.value & 0xff; n6[Symbol.dispose](); - return _022; + return _018; }); }), undefined, true); rs.byteVecPush(out, arc.weak & 0xff); @@ -3444,11 +3381,11 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { if (rs.sliceFrom(nums3.items).length >= 2) { const first: rs.Place = rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 0)); rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, rs.usizeSub(nums3.items.length, 1))), first) & 0xff); - const _172: rs.usize = nums3.items.length; + const _148: rs.usize = nums3.items.length; let iterStart: rs.usize = 0; - const iterEnd: rs.usize = _172; - while ((_175 = iterStart++) < iterEnd) { - rs.byteVecPush(out, rs.ptrAdd(first, _175).v & 0xff); + const iterEnd: rs.usize = _148; + while ((_151 = iterStart++) < iterEnd) { + rs.byteVecPush(out, rs.ptrAdd(first, _151).v & 0xff); } rs.byteVecPush(out, rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v & 0xff); rs.ptrAdd(rs.arrayAsPtr(nums3.items, rs.idx(nums3.items, 1)), -1).v = 9; @@ -3456,30 +3393,30 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { } const v: rs.ByteVec = rs.byteSliceToByteVec(data); rs.byteVecPush(out, vecLen(v) & 0xff); - const _206: rs.ByteVec = rs.iterToByteVec(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), undefined, true); - refilled(v, _206, bits); + const _182: rs.ByteVec = rs.iterToByteVec(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), undefined, true); + refilled(v, _182, bits); rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); let iterStart2: rs.u8 = 0; const iterEnd2: rs.u8 = 3; - while ((_215 = iterStart2++) < iterEnd2) { - const how: rs.u8 = _215; - const w3: rs.ByteVec = rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b7: rs.u8): rs.u8 => { - return b7 ^ how; + while ((_191 = iterStart2++) < iterEnd2) { + const how: rs.u8 = _191; + const w2: rs.ByteVec = rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b6: rs.u8): rs.u8 => { + return b6 ^ how; }), undefined, true); - const _227: rs.ByteVec = rs.byteVecOf(bits, rs.usizeAdd(how as rs.usize, 1)); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(replaced(v, w3, _227, how))), undefined, true); + const _203: rs.ByteVec = rs.byteVecOf(bits, rs.usizeAdd(how as rs.usize, 1)); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(replaced(v, w2, _203, how))), undefined, true); rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(v)), undefined, true); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(w3)), undefined, true); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteVecView(w2)), undefined, true); rs.byteVecPush(out, 255); } - const iter7: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); + const iter6: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _242: [rs.usize, rs.u8] | rs.Done = iter7.next(); - if (_242 === rs.DONE) { + const _218: [rs.usize, rs.u8] | rs.Done = iter6.next(); + if (_218 === rs.DONE) { break; } - const i3: rs.usize = _242[0]; - const b: rs.u8 = _242[1]; + const i3: rs.usize = _218[0]; + const b: rs.u8 = _218[1]; rs.byteVecPush(out, behind(b as rs.u32, i3 & 0xff) & 0xff); rs.byteVecPush(out, cellBehind(then$01qpu7s(rs.u8Rem(b, 3) !== 0, (): rs.Cell => { return new rs.Cell(b as rs.u32); @@ -3602,20 +3539,6 @@ export function inlineMoved(r: rs.RefCell>, o: rs.OnceCell, q: rs.VecDeque): [rs.Vec, rs.VecDeque, rs.u32 | undefined] { - const v2: rs.Vec = v; - const oldV: rs.Vec = copy$Vec_u32(v2); - rs.vecSetLen(v2, 0, 4, (): rs.u32 => { - return (undefined as unknown) as rs.u32; - }); - return [oldV, copy$collections_VecDeque_u32(q), rs.dequePopFront(q)] as [rs.Vec, rs.VecDeque, rs.u32 | undefined]; -} - /** * A cell written whole through a pointer from the shared reference given, which may be * the cell `Cell::from_mut` makes of another place. @@ -3752,30 +3675,12 @@ export function movedOut(data: rs.ByteSlice, out: rs.ByteVec): void { } rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(oldC.v & 0xff, x & 0xff)), undefined, true); } - const nums: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_23: rs.u8): rs.u32 => { - return _23 as rs.u32; - }), undefined, 4, true); - const _199: [rs.Vec, rs.VecDeque, rs.u32 | undefined] = shrunkMoved(rs.vecFrom(nums.items.slice()), rs.dequeFromVec(rs.iterToVec(rs.iterSlice(nums.items), undefined, 4, true), 4) as rs.VecDeque); - const oldV: rs.Vec = _199[0]; - const oldQ: rs.VecDeque = _199[1]; - const popped: rs.u32 | undefined = _199[2]; - rs.byteVecPush(out, oldV.items.length & 0xff); - rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(oldV.items), (_24: rs.u32): rs.u8 => { - return _24 & 0xff; - }), undefined, true); - rs.byteVecPush(out, option_mapOr$07pvkk0(popped, 255, (n3: rs.u32): rs.u8 => { - return n3 & 0xff; - })); - rs.byteVecPush(out, oldQ.n & 0xff); - rs.iterExtendBytes(out, rs.iterMap(rs.dequeIter(oldQ), (_27: rs.u32): rs.u8 => { - return _27 & 0xff; - }), undefined, true); const map: rs.HashMap = rs.hashWithCapacity(rs.usizeMul(data.length, 8), 117440512) as rs.HashMap; - const _232: rs.Iter<[rs.u32, rs.u32]> = rs.iterMap(rs.iterSlice(nums.items), (_29: rs.u32): [rs.u32, rs.u32] => { - const n5: rs.u32 = _29; - return [n5, n5] as [rs.u32, rs.u32]; + const _199: rs.Iter<[rs.u32, rs.u32]> = rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_23: rs.u8): [rs.u32, rs.u32] => { + const b4: rs.u8 = _23; + return [b4 as rs.u32, b4 as rs.u32] as [rs.u32, rs.u32]; }); - rs.hashExtend(map, _232, undefined, undefined, undefined, 117440512); + rs.hashExtend(map, _199, undefined, undefined, undefined, 117440512); mapRewritten(map); rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(rs.hashLen(map) & 0xff, rs.hashCapacity(map) & 0xff)), undefined, true); for (const b2 of rs.byteSliceView(data)) { diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index e9021abe1..c3ddb6092 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -153,7 +153,7 @@ impl List { } } -impl List { +impl List> { fn each(&self, out: &mut Vec) { let Some(guard) = self.guard else { return; @@ -162,7 +162,7 @@ impl List { while at != guard { let (key, value) = unsafe { at.as_ref().entry.assume_init_ref() }; out.push(*key); - out.extend_from_slice(value.as_bytes()); + out.extend_from_slice(value); at = unsafe { at.as_ref().link.value.next }; } } @@ -227,23 +227,23 @@ impl Drop for List { /// Steps on a list of the operation and key each pair of bytes picks, putting what each /// finds and then the list in order. pub fn lists(data: &[u8], out: &mut Vec) { - let mut list = List::::new(); + let mut list = List::>::new(); let mut data = data; while let [op, key, rest @ ..] = data { data = rest; let (op, key) = (*op, *key % 8); match op % 4 { - 0 => list.push_back(key, String::from(["", "v", "vv"][usize::from(op % 3)])), + 0 => list.push_back(key, [&b""[..], b"v", b"vv"][usize::from(op % 3)].to_vec()), 1 => match list.remove(key) { Some((k, v)) => { out.push(k); - out.extend_from_slice(v.as_bytes()); + out.extend_from_slice(&v); } None => out.push(0xff), }, 2 => out.push(list.to_front(key) as u8), _ => match list.get_mut(key) { - Some(value) => value.push('w'), + Some(value) => value.push(b'w'), None => out.push(0xfe), }, } @@ -2548,10 +2548,6 @@ pub fn behind_refs(data: &[u8], out: &mut Vec) { let bits = data.first().copied().unwrap_or(0); let nums: Vec = data.iter().map(|&b| u32::from(b) * 3).collect(); out.extend(drained(nums.clone(), bits).iter().map(|&n| n as u8)); - let words: Vec = data.iter().map(|b| b.to_string()).collect(); - for w in drained(words.clone(), bits) { - out.extend(w.bytes()); - } let runs: Vec> = data.chunks(3).map(<[u8]>::to_vec).collect(); for r in drained(runs.clone(), bits) { out.push(r.len() as u8); @@ -2568,12 +2564,6 @@ pub fn behind_refs(data: &[u8], out: &mut Vec) { *n % 2 == 0 }); out.extend(nums.iter().map(|&n| n as u8)); - let mut words = words; - retained(&mut words, |w| { - w.push('!'); - w.len() % 2 == 0 - }); - out.extend(words.iter().flat_map(|w| w.bytes())); let mut runs = runs; retained(&mut runs, |r| { r.push(7); @@ -2764,22 +2754,6 @@ fn inline_moved(r: &RefCell>, o: &OnceCell>) -> (RefCell, - mut q: std::collections::VecDeque, -) -> (Vec, std::collections::VecDeque, Option) { - let mut v = mem::ManuallyDrop::new(v); - let old_v = unsafe { ptr::read(&*v) }; - unsafe { v.set_len(0) }; - let old_q = unsafe { ptr::read(&q) }; - let popped = q.pop_front(); - mem::forget(q); - (old_v, old_q, popped) -} - /// A cell written whole through a pointer from the shared reference given, which may be /// the cell `Cell::from_mut` makes of another place. #[inline(never)] @@ -2866,15 +2840,8 @@ pub fn moved_out(data: &[u8], out: &mut Vec) { } out.extend([old_c.get() as u8, x as u8]); } - let nums: Vec = data.iter().map(|&b| u32::from(b)).collect(); - let (old_v, old_q, popped) = shrunk_moved(nums.clone(), nums.iter().copied().collect()); - out.push(old_v.len() as u8); - out.extend(old_v.iter().map(|&n| n as u8)); - out.push(popped.map_or(0xff, |n| n as u8)); - out.push(old_q.len() as u8); - out.extend(old_q.iter().map(|&n| n as u8)); let mut map = std::collections::HashMap::::with_capacity(data.len() * 8); - map.extend(nums.iter().map(|&n| (n, n))); + map.extend(data.iter().map(|&b| (u32::from(b), u32::from(b)))); map_rewritten(&mut map); out.extend([map.len() as u8, map.capacity() as u8]); for &b in data { diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index f5c70022b..f96c6f6c3 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -6,10 +6,18 @@ fixtures/reject/src/alloc.rs:alloc_retyped+8:21: memory from `alloc` cast to poi fixtures/reject/src/alloc.rs:alloc_retyped+10:17: memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:allocated+4:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as fixtures/reject/src/alloc.rs:allocated+5:11: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as +fixtures/reject/src/alloc.rs:beside_reference+2:14: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:beside_reference+2:35: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:beside_reference+2:44: a slice of a length the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:bytes_as_values+3:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` to one to `pointers::Halves`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another +fixtures/reject/src/alloc.rs:collection_moved+2:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+4:23: a read moving out a `std::collections::VecDeque`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+10:11: a copy between pointers to `(std::string::String, u8)`, which JS keeps as an object that knows no array it is in +fixtures/reject/src/alloc.rs:collection_moved+11:28: a read moving out a `(std::string::String, u8)`, which holds a `std::string::String`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+14:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 6feb42e25..295079e33 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -10,10 +10,18 @@ fixtures/reject/src/alloc.rs:alloc_retyped+8:21: memory from `alloc` cast to poi fixtures/reject/src/alloc.rs:alloc_retyped+10:17: memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:allocated+4:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as fixtures/reject/src/alloc.rs:allocated+5:11: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as +fixtures/reject/src/alloc.rs:beside_reference+2:14: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:beside_reference+2:35: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:beside_reference+2:44: a slice of a length the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:bytes_as_values+3:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` to one to `pointers::Halves`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another +fixtures/reject/src/alloc.rs:collection_moved+2:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+4:23: a read moving out a `std::collections::VecDeque`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+10:11: a copy between pointers to `(std::string::String, u8)`, which JS keeps as an object that knows no array it is in +fixtures/reject/src/alloc.rs:collection_moved+11:28: a read moving out a `(std::string::String, u8)`, which holds a `std::string::String`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+14:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust diff --git a/ts/fixtures/reject/src/alloc.rs b/ts/fixtures/reject/src/alloc.rs index 62f740cd6..2e4e7de72 100644 --- a/ts/fixtures/reject/src/alloc.rs +++ b/ts/fixtures/reject/src/alloc.rs @@ -63,6 +63,39 @@ pub fn peeked_moved(cells: Vec>) -> u32 { copy.next().map_or(0, |cell| cell.get()) } +/// Collections whose JS object keeps its length with its storage, moved out through +/// pointers while the original lives on: by `ptr::read`, `ptr::copy` within a `Vec` as +/// smallvec's `retain` moves its elements, `MaybeUninit::assume_init_read` of a tuple +/// holding one, and `ManuallyDrop::take`. Rust's copy shares the storage and keeps a +/// length of its own, which no JS copy does. +pub fn collection_moved(v: Vec, mut q: std::collections::VecDeque, s: String) -> usize { + let mut v = std::mem::ManuallyDrop::new(v); + let old_v = unsafe { std::ptr::read(&*v) }; + unsafe { v.set_len(0) }; + let old_q = unsafe { std::ptr::read(&q) }; + q.pop_front(); + std::mem::forget(q); + let mut words = vec![s.clone(), s.clone()]; + words[1].push('!'); + unsafe { + std::ptr::copy(words.as_ptr().add(1), words.as_mut_ptr(), 1); + words.set_len(1); + } + let pair = std::mem::MaybeUninit::new((s, 0u8)); + let old_s = unsafe { pair.assume_init_read() }; + let mut taken = std::mem::ManuallyDrop::new(vec![1u32]); + let old_t = unsafe { std::mem::ManuallyDrop::take(&mut taken) }; + old_v.len() + old_q.len() + words[0].len() + old_s.0.len() + old_t.len() +} + +/// A pointer from a reference the body is given, which JS keeps in no array, read through +/// past it by a constant wrapping offset and one past it, and viewed as a slice of a +/// length the body doesn't fix: each reaches the neighbour Tree Borrows allows it to. +pub fn beside_reference(r: &u32, n: usize) -> u32 { + let p = std::ptr::from_ref(r); + unsafe { *p.wrapping_add(1) + *p.add(1) + std::slice::from_raw_parts(p, n).iter().sum::() } +} + /// A pointer from a reference the body is given, which JS keeps in no array, moved past /// it. pub fn past_reference(r: &u32) -> u32 { diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 51b96c113..aa68b8a22 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -3,7 +3,9 @@ roots = [ "alloc::addressed", "alloc::alloc_retyped", "alloc::allocated", + "alloc::beside_reference", "alloc::bytes_as_values", + "alloc::collection_moved", "alloc::infallible_cell", "alloc::lazy_moved", "alloc::past_reference", diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 30421f638..616514c8a 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -4,7 +4,7 @@ //! JS shares an object where Rust copies its value, so a `Copy` value that is a JS object //! the crate's code writes into in place, or compares pointers to, is copied where Rust //! copies it, as is each such object a read through a pointer moves out, a library -//! object such as a `Vec` included, one object deep. A function made for its type copies +//! object such as a `RefCell` included, one object deep. A function made for its type copies //! what it holds by value and shares what it holds by reference, as Rust does. A value of //! a type nothing writes into or compares pointers to never changes once made, so sharing //! it is a copy no one can tell apart. @@ -34,12 +34,9 @@ use crate::ts::{self, Expr, Key, Param, Stmt, StmtKind}; struct Inline<'tcx> { /// Each field of its JS object holding a value inline, and the type held there. fields: Vec<(&'static str, Ty<'tcx>)>, - /// Each field holding the JS array of its storage, whose length or slots the runtime - /// changes where Rust changes only the object's own length, as a `Vec`'s `set_len` and - /// a `VecDeque`'s `pop_front` do: copied, sharing its items. - storage: Vec<&'static str>, - /// The types it holds where no copy reaches, a hash map's hasher. - unreachable: Vec>, + /// The types it holds inline that its copy shares, a hash map's hasher, which a move + /// out of it through a pointer counts as it counts the collection. + shared: Vec>, } impl<'a, 'tcx> Translator<'a, 'tcx> { @@ -84,7 +81,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { .fields .into_iter() .map(|(_, t)| t) - .chain(inline.unreachable) + .chain(inline.shared) .any(|t| self.changes(t)), // Any type it is over may be held inline, which its copy refuses. None => args.types().any(|t| self.changes(t)), @@ -105,21 +102,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// What a library object `ty` holds inline rather than on the heap, which a copy of the /// object one object deep shares: each field of its JS object holding such a value and /// the type held there, a cell's, a lock's, a range's ends, or a one-time cell's, which - /// holds nothing until set; the JS arrays holding a collection's storage whose length - /// is its own; and the types it holds where no copy reaches, a hash map's hasher. A - /// collection's items, and what a `Weak` points at, are on the heap, which Rust's read - /// shares too. `None` for any other library object, such as a `LazyCell` or a - /// `Peekable`, whose inline state only its runtime class knows, so a read that would + /// holds nothing until set; and the types it holds where no copy reaches, a hash map's + /// hasher. A collection's items, and what a `Weak` points at, are on the heap, which + /// Rust's read shares too. `None` for any other library object, such as a `LazyCell` or + /// a `Peekable`, whose inline state only its runtime class knows, so a read that would /// copy one is refused. fn inline_held(&self, ty: Ty<'tcx>) -> Option> { let ty::Adt(adt, args) = ty.kind() else { return None; }; let at = |i: usize| args.type_at(i); - let storage = |field: &'static str| Inline { - storage: vec![field], - ..Inline::default() - }; let fields = match Std::of(self.tcx, adt.did()) { Some(Std::Cell) => vec![("v", at(0))], Some(Std::RefCell | Std::Mutex) => vec![("value", at(0))], @@ -127,11 +119,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some(Std::Range) => vec![("start", at(0)), ("end", at(0))], Some(Std::RangeInclusive) => vec![("lo", at(0)), ("hi", at(0))], Some(Std::RangeFrom) => vec![("start", at(0))], - // A `Vec`'s length is apart from its bytes. - Some(Std::Vec | Std::BinaryHeap) if !super::byte(at(0)) => return Some(storage("items")), - Some(Std::VecDeque) => return Some(storage("b")), Some( Std::Vec + | Std::VecDeque | Std::String | Std::LinkedList | Std::BinaryHeap @@ -143,13 +133,13 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ) => Vec::new(), Some(Std::HashMap) => { return Some(Inline { - unreachable: vec![at(2)], + shared: vec![at(2)], ..Inline::default() }); } Some(Std::HashSet) => { return Some(Inline { - unreachable: vec![at(1)], + shared: vec![at(1)], ..Inline::default() }); } @@ -161,6 +151,36 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }) } + /// The collection held by value in a `ty` that a move of one out of where it is kept, as + /// `ptr::read` makes, would copy, if any: a `Vec` of other than bytes, a `VecDeque`, a + /// `String`, a map, a set, or a list, whose JS object keeps its length with its storage, + /// so no copy splits from the original as Rust's read does, sharing the storage while + /// each keeps a length of its own. A safe move leaves nothing pointing into the + /// original, which a copy one object deep is exact for. + pub fn unsplit(&self, ty: Ty<'tcx>) -> Option> { + by_value(self.tcx, self.typing_env(), ty).find(|&part| { + let ty::Adt(adt, args) = part.kind() else { + return false; + }; + let joined = match Std::of(self.tcx, adt.did()) { + Some(Std::Vec) => !super::byte(args.type_at(0)), + Some( + Std::VecDeque + | Std::BinaryHeap + | Std::String + | Std::LinkedList + | Std::BTreeMap + | Std::BTreeSet + | Std::HashMap + | Std::HashSet + | Std::HashTable, + ) => true, + _ => false, + }; + joined && self.changes(part) + }) + } + /// Whether the runs before compared pointers to a `ty`, or to a type it holds by value, /// so each copy of one is at an address of its own, as in Rust, wherever it is read. pub fn held_addressed(&self, ty: Ty<'tcx>) -> bool { @@ -441,12 +461,6 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ); return ret(v()); }; - if inline.unreachable.into_iter().any(|t| self.changes(t)) { - self.err( - span, - format!("a read moving out a `{ty}`, which holds inline a value JS can't copy apart"), - ); - } // A cell of its own, as `Cell::from_mut`'s place reads and writes another's. if std == Std::Cell { let held = self.snapshot(v().member("v"), args.type_at(0), span); @@ -454,15 +468,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } let fields: Vec<_> = inline.fields.into_iter().filter(|&(_, t)| self.changes(t)).collect(); let copy = Expr::rs("objectCopy", vec![v()]); - if fields.is_empty() && inline.storage.is_empty() { + if fields.is_empty() { return ret(copy); } let c = || Expr::ident("c"); let mut body = vec![StmtKind::decl(false, "c", None, Some(copy)).at(None)]; - for field in inline.storage { - let items = c().member(field).member("slice").call(Vec::new()); - body.push(StmtKind::Expr(c().member(field).assign(items)).at(None)); - } for (field, t) in fields { let read = self.snapshot(c().member(field), t, span); body.push(StmtKind::Expr(c().member(field).assign(read)).at(None)); diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 4de0a3b80..4b6fcd2bf 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -64,8 +64,10 @@ struct Origin<'tcx> { impl<'tcx> Fx<'_, '_, '_, 'tcx> { /// Why the call of std's `def_id` would fail at run time where Rust's is defined, if the /// body proves it would: `offset_from` between pointers JS keeps in different arrays, or - /// a pointer, slice, copy, or write reaching out of an array of one or a copied byte, or - /// moved by an offset or a count the body doesn't fix from an array of one. + /// a pointer, slice, copy, or write reaching out of an array of one or a copied byte. + /// From an array of one, every offset, count, and length must be one the body fixes and + /// that stays within it, a pointer still at its element: past it, Rust may reach the + /// neighbour the reference was borrowed beside, which no body here shows. pub(super) fn stray( &self, def_id: rustc_hir::def_id::DefId, @@ -102,6 +104,12 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { let Some(index) = o.index else { return unfixed(o, &format!("{what} moved by an offset")); }; + // A pointer from an array of one stays at its element: one past it is Rust's + // too, but a read through it, which reaches the neighbour, is where no check is. + let n = match o.array { + Array::Lone(_) => n.or(Some(1)), + _ => n, + }; let past = o.array.len().is_some_and(|len| index + n.unwrap_or(0) > len); (index < 0 || past).then(|| { format!( @@ -138,23 +146,25 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { let o = self.moved(self.origin(arg(0)?)?, name.as_str(), arg(1)?); out(o, None, "a pointer") } - // A wrapping offset may leave the array while nothing reads through it. + // A wrapping offset may leave any other array while nothing reads through it. ("ptr" | "NonNull", "wrapping_add" | "wrapping_sub" | "wrapping_offset") => { let o = self.origin(arg(0)?)?; - match self.const_int(arg(1)?) { - Some(_) => None, - None => unfixed(o, "a pointer moved by an offset"), - } + let lone = matches!(o.array, Array::Lone(_)); + lone.then(|| out(self.moved(o, name.as_str(), arg(1)?), None, "a pointer"))? } ("raw", "from_raw_parts" | "from_raw_parts_mut") | ("ptr", "slice_from_raw_parts" | "slice_from_raw_parts_mut") | ("NonNull", "slice_from_raw_parts") => { let o = self.origin(arg(0)?)?; - // A raw pointer to a slice reads nothing until a read through it. - if owner != "raw" { + // A raw pointer to a slice of any other array reads nothing until a read + // through it. + if owner != "raw" && !matches!(o.array, Array::Lone(_)) { return None; } - out(o, Some(self.const_int(arg(1)?)?), "a slice") + match self.const_int(arg(1)?) { + Some(n) => out(o, Some(n), "a slice"), + None => unfixed(o, "a slice of a length"), + } } ( "ptr", @@ -347,10 +357,10 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { }) } - /// `o` moved by the offset `op` as `add`, `sub`, or `offset` moves it. + /// `o` moved by the offset `op` as `add`, `sub`, or `offset`, or a wrapping one, moves it. fn moved(&self, o: Origin<'tcx>, name: &str, op: &Operand<'tcx>) -> Origin<'tcx> { let n = self.const_int(op).map(|n| match name { - "sub" => -n, + "sub" | "wrapping_sub" => -n, _ => n, }); Origin { diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index a48e9f130..69046dd1a 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -846,7 +846,10 @@ pub fn call<'tcx>( } } // A shared reference to a value JS copies is the value. - ("MaybeUninit", "assume_init_read") => b.t.snapshot(arg(), pointee(), span), + ("MaybeUninit", "assume_init_read") => match unsplit(b, pointee(), span) { + Some(refused) => refused, + None => b.t.snapshot(arg(), pointee(), span), + }, ("MaybeUninit", "assume_init_drop") | ("ManuallyDrop", "drop") => { let p = arg(); let value = match kind(b, pointee()) { @@ -858,6 +861,9 @@ pub fn call<'tcx>( // What it holds, which it leaves for nothing to read or drop again. ("ManuallyDrop", "take") => { let p = arg(); + if let Some(refused) = unsplit(b, pointee(), span) { + return Ok(Some(refused)); + } let value = match kind(b, pointee()) { Some(Pointee::Place) => p.member("v"), _ => p, @@ -945,6 +951,8 @@ pub fn call<'tcx>( Expr::undefined() } else if let Some(why) = unmodeled(b, pointee(), "a copy between pointers to") { b.err(span, why) + } else if let Some(refused) = unsplit(b, pointee(), span) { + refused } else { let mut args = vec![src, dst, n]; args.extend(b.t.snapshot_fn(pointee(), span)); @@ -1175,8 +1183,27 @@ pub fn raw_pointee<'tcx>(tcx: ty::TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option } } +/// The refusal of a move of a `ty` out of where it is kept, as `ptr::read` makes, that +/// would copy a collection whose JS storage can't split from the original as Rust's does. +fn unsplit<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, span: Span) -> Option { + let held = b.t.unsplit(ty)?; + let what = match held == ty { + true => String::new(), + false => format!(", which holds a `{held}`"), + }; + Some(b.err( + span, + format!( + "a read moving out a `{ty}`{what}, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does" + ), + )) +} + /// The value `p`, a pointer to `ty`, points at, as a value of its own. fn read<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, ty: Ty<'tcx>, span: Span) -> Expr { + if let Some(refused) = unsplit(b, ty, span) { + return refused; + } let value = match b.t.pointee(ty) { // The value a pointer made here of a shared reference holds. Pointee::Place => match p.unlocated() { From 3d5165c718802c231d73864aaaa9bc6d2ee3f177 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 12:21:39 -0800 Subject: [PATCH 26/29] test(ts): refuse a collection's ptr::copy within a Vec Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.txt | 6 +++--- ts/fixtures/reject/expected.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index f96c6f6c3..079e84442 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -15,9 +15,9 @@ fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:collection_moved+2:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does fixtures/reject/src/alloc.rs:collection_moved+4:23: a read moving out a `std::collections::VecDeque`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does -fixtures/reject/src/alloc.rs:collection_moved+10:11: a copy between pointers to `(std::string::String, u8)`, which JS keeps as an object that knows no array it is in -fixtures/reject/src/alloc.rs:collection_moved+11:28: a read moving out a `(std::string::String, u8)`, which holds a `std::string::String`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does -fixtures/reject/src/alloc.rs:collection_moved+14:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+10:3: a read moving out a `std::string::String`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+14:28: a read moving out a `(std::string::String, u8)`, which holds a `std::string::String`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+16:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 295079e33..cf3a07a2d 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -19,9 +19,9 @@ fixtures/reject/src/alloc.rs:bytes_as_values+5:16: a cast from a pointer to `u8` fixtures/reject/src/alloc.rs:bytes_as_values+7:15: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:collection_moved+2:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does fixtures/reject/src/alloc.rs:collection_moved+4:23: a read moving out a `std::collections::VecDeque`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does -fixtures/reject/src/alloc.rs:collection_moved+10:11: a copy between pointers to `(std::string::String, u8)`, which JS keeps as an object that knows no array it is in -fixtures/reject/src/alloc.rs:collection_moved+11:28: a read moving out a `(std::string::String, u8)`, which holds a `std::string::String`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does -fixtures/reject/src/alloc.rs:collection_moved+14:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+10:3: a read moving out a `std::string::String`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+14:28: a read moving out a `(std::string::String, u8)`, which holds a `std::string::String`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does +fixtures/reject/src/alloc.rs:collection_moved+16:23: a read moving out a `std::vec::Vec`, a collection whose JS object keeps its length with its storage, which no copy splits from the original as Rust's read does fixtures/reject/src/alloc.rs:infallible_cell+1:11: a raw pointer to `std::result::Result, !>` behind a reference, which JS can't write a whole value into in place fixtures/reject/src/alloc.rs:lazy_moved+2:22: a read moving out a `std::cell::LazyCell`, a library object whose inline state only its runtime class knows, which JS can't copy apart fixtures/reject/src/alloc.rs:past_reference+1:30: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust From 44bfc2a1a9f2f618e97e9e81cc54aeeb7353aafc Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 12:34:23 -0800 Subject: [PATCH 27/29] fix(ts): check reads one past a lone place, not the pointer A pointer one past an array of one is valid Rust, which main's elements fixture moves through and back; a read or write there, by a call or a deref, is what reaches the neighbour, so that is refused, and a wrapping offset from one is traced so the read through it is checked. Fixtures whose pointer moves of `String`s are now refused move `Vec`s. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 2 +- ts/fixtures/lang/conversions/expected/lib.ts | 4 +- ts/fixtures/lang/elements/expected/lib.ts | 2 +- ts/fixtures/lang/elements/expected/spare.ts | 114 ++++++++++++------- ts/fixtures/lang/elements/src/spare.rs | 19 +++- ts/fixtures/reject/census.txt | 4 +- ts/fixtures/reject/expected.txt | 4 +- ts/src/lower/mir/origin.rs | 111 ++++++++++++------ ts/src/lower/mir/place.rs | 4 +- 9 files changed, 176 insertions(+), 88 deletions(-) diff --git a/ts/README.md b/ts/README.md index 2d0ee4e9f..2e63bbf4c 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: 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` | `rs.Cow`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows, `rs.Cow>` holding the arrays of a slice of arrays; `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`, `Reverse`, `NonZero`, `AssertUnwindSafe` | 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`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | 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` 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`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust, but a byte variable whose `&raw` the body takes, or that of a reference to it made there, as `&mut b as *mut u8` does, is element 0 of a `Uint8Array` of its own, which its raw, mutable, and shared slices view as they do any other bytes; JS keeps an array of one, and a shared subslice of a byte in no array, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, a pointer it shows moved from a value in no array off it, or by an offset, or a copy, write, or slice of a count or length, it doesn't fix, and `offset_from` between pointers the body shows JS keeps in different arrays, are refused, and through a pointer from elsewhere any of these, a raw or mutable slice of a byte in no array, such as a field's, 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. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `RefCell` or a `Vec` one object deep, with what a cell, lock, or range holds inline, and one with interior mutability, such as a `Cell`, always; a collection whose JS object keeps its length with its storage, such as a `Vec` of other than bytes, a `VecDeque`, a `String`, a map, or a set, which no copy splits from the original as Rust's read does, and any other library object such as a `LazyCell` or a `Peekable`, whose inline state only its runtime class knows, are refused where the read would copy them | +| `*const T`, `*mut T`, `NonNull` | 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` 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`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust, but a byte variable whose `&raw` the body takes, or that of a reference to it made there, as `&mut b as *mut u8` does, is element 0 of a `Uint8Array` of its own, which its raw, mutable, and shared slices view as they do any other bytes; JS keeps an array of one, and a shared subslice of a byte in no array, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, a read or write it shows one past a value in no array, a pointer it shows moved from one by an offset, wrapping or not, or a copy, write, or slice of a count or length, it doesn't fix, and `offset_from` between pointers the body shows JS keeps in different arrays, are refused, and through a pointer from elsewhere any of these, a raw or mutable slice of a byte in no array, such as a field's, 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. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. 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. One made from a reference, by `&raw const *r`, `as`, `ptr::from_ref` or `from_mut`, or `NonNull::from`, is where the body shows the reference borrowed, as the `rs.AtIndex` of the element `&v[i]` lends, and else a place of its own: holding the value of a shared reference, which nothing may write while the pointer lives, or an `rs.placeIn` writing a whole value into the object a `&mut` is in place. 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 that moves a value out, by `ptr::read`, `replace`, `swap`, or `copy`, copies each object it holds at any depth that the crate writes into in place, a library object such as a `RefCell` or a `Vec` one object deep, with what a cell, lock, or range holds inline, and one with interior mutability, such as a `Cell`, always; a collection whose JS object keeps its length with its storage, such as a `Vec` of other than bytes, a `VecDeque`, a `String`, a map, or a set, which no copy splits from the original as Rust's read does, and any other library object such as a `LazyCell` or a `Peekable`, whose inline state only its runtime class knows, are refused where the read would copy them | | `MaybeUninit` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined`, as `Box::new_uninit` and each of `Box::new_uninit_slice`'s is; a slice's `write_clone_of_slice` clones into its slots without dropping what they held. `Box::new_zeroed`'s holds the value of zero bytes in each part they are one of, such as a number, `false`, `None`, a null pointer, or an enum's variant of a tag or niche of zero, and `undefined` in each part they are none of, such as a reference, for a write to fill before a read | | `ManuallyDrop` | `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 | diff --git a/ts/fixtures/lang/conversions/expected/lib.ts b/ts/fixtures/lang/conversions/expected/lib.ts index c52db795d..a448b3248 100644 --- a/ts/fixtures/lang/conversions/expected/lib.ts +++ b/ts/fixtures/lang/conversions/expected/lib.ts @@ -10,9 +10,7 @@ export function copy$String(v: rs.String): rs.String { } export function copy$Vec_u32(v: rs.Vec): rs.Vec { - const c = rs.objectCopy(v); - c.items = c.items.slice(); - return c; + return rs.objectCopy(v); } export function copy$Vec_u8(v: rs.ByteVec): rs.ByteVec { diff --git a/ts/fixtures/lang/elements/expected/lib.ts b/ts/fixtures/lang/elements/expected/lib.ts index 408c432b1..fb0fc40fd 100644 --- a/ts/fixtures/lang/elements/expected/lib.ts +++ b/ts/fixtures/lang/elements/expected/lib.ts @@ -9,7 +9,7 @@ export function copy$Option_tuple2_u8_u8(v: [rs.u8, rs.u8] | undefined): [rs.u8, return v === undefined ? v : copy$tuple2_u8_u8(v); } -export function copy$String(v: rs.String): rs.String { +export function copy$Vec_u8(v: rs.ByteVec): rs.ByteVec { return rs.objectCopy(v); } diff --git a/ts/fixtures/lang/elements/expected/spare.ts b/ts/fixtures/lang/elements/expected/spare.ts index 103efd939..84498f4b6 100644 --- a/ts/fixtures/lang/elements/expected/spare.ts +++ b/ts/fixtures/lang/elements/expected/spare.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$String, copy$mem_MaybeUninit_tuple2_u8_u32, copy$tuple2_u8_u32 } from "./lib.ts"; +import { copy$Vec_u8, copy$mem_MaybeUninit_tuple2_u8_u32, copy$tuple2_u8_u32 } from "./lib.ts"; import { thenSome$1gvhopg } from "./core/bool.ts"; import { mapOr$17z0l8m as option_mapOr$17z0l8m } from "./core/option.ts"; import * as Noted from "./spare.Noted.ts"; @@ -25,6 +25,15 @@ export function put$spare_Item_String(self: rs.String, out: rs.ByteVec): void { rs.byteVecPush(out, 255); } +export function of$spare_Item_Vec_u8(n: rs.u8): rs.ByteVec { + return rs.byteVecOf(n, rs.u8Rem(n, 3) as rs.usize); +} + +export function put$spare_Item_Vec_u8(self: rs.ByteVec, out: rs.ByteVec): void { + rs.byteVecExtendFromSlice(out, rs.byteVecSlice(self)); + rs.byteVecPush(out, 255); +} + export function of$spare_Item_Option_u16(n: rs.u8): rs.u16 | undefined { return thenSome$1gvhopg(rs.u8Rem(n, 3) !== 0, ((n as rs.u16) << 4) & 0xffff); } @@ -92,6 +101,29 @@ export function state$String(v: rs.Vec, out: rs.ByteVec): void { } } +/** The length, capacity, and room of `v`, how many `Noted`s dropped, and its items. */ +export function state$Vec_u8(v: rs.Vec, out: rs.ByteVec): void { + const iter: rs.Iter = rs.iterSlice([v.items.length, rs.vecCapacity(v, 12), rs.vecSpare(v, 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; + }).length]); + while (true) { + const _13: rs.usize | rs.Done = iter.next(); + if (_13 === rs.DONE) { + break; + } + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes(_13 as rs.u32).reverse())); + } + rs.byteVecPush(out, rs.atomicLoad(DROPS, 0)); + const iter2: rs.Iter = rs.iterSlice(v.items); + while (true) { + const _31: rs.ByteVec | rs.Done = iter2.next(); + if (_31 === rs.DONE) { + break; + } + put$spare_Item_Vec_u8(_31, out); + } +} + /** The length, capacity, and room of `v`, how many `Noted`s dropped, and its items. */ export function state$spare_Noted(v: rs.Vec, out: rs.ByteVec): void { const iter: rs.Iter = rs.iterSlice([v.items.length, rs.vecCapacity(v, 1), rs.vecSpare(v, 1, (): Noted.Noted => { @@ -324,12 +356,12 @@ export function pointers$Option_u16(data: rs.ByteSlice, out: rs.ByteVec): void { * `Drain` does; a filter that shortens it to nothing and writes back what it keeps, as * std's `retain` does; grown, cut, and refilled. */ -export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { +export function pointers$Vec_u8(data: rs.ByteSlice, out: rs.ByteVec): void { let _43!: rs.usize; let _165!: rs.usize; let _194!: rs.usize; const ops: Ops.Ops = { _0Data: data.data, _0Start: data.start, _0Len: data.length }; - let v: rs.Vec = rs.vecWithCapacity(rs.u8Rem(Ops.byte(ops), 12) as rs.usize, 12); + let v: rs.Vec = rs.vecWithCapacity(rs.u8Rem(Ops.byte(ops), 12) as rs.usize, 12); while (!(ops._0Len === 0)) { const _13: [rs.u8, rs.u8] = [Ops.byte(ops), Ops.byte(ops)]; const op: rs.u8 = _13[0]; @@ -340,59 +372,59 @@ export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { b2: { const _35: rs.u8 = rs.u8Rem(op, 8); if (_35 === 0) { - const end: rs.Place = rs.ptrAdd(rs.vecAsPtr(v, 12), len); + const end: rs.Place = rs.ptrAdd(rs.vecAsPtr(v, 12), len); let iterStart: rs.usize = 0; const iterEnd: rs.usize = k; while ((_43 = iterStart++) < iterEnd) { const i: rs.usize = _43; - rs.ptrAdd(end, i).v = of$spare_Item_String(rs.u8WrappingAdd(n, i & 0xff)); + rs.ptrAdd(end, i).v = of$spare_Item_Vec_u8(rs.u8WrappingAdd(n, i & 0xff)); } if (k >= 2) { - const _54: rs.Slice = new rs.RawSlice(end, k).array(2); + const _54: rs.Slice = new rs.RawSlice(end, k).array(2); rs.sliceSwap(_54, 0, 1); - put$spare_Item_String(_54.data[_54.start], out); + put$spare_Item_Vec_u8(_54.data[_54.start], out); } - const moved: rs.Vec = rs.replaceIn(v, new rs.Vec()); + const moved: rs.Vec = rs.replaceIn(v, new rs.Vec()); for (const _71 of rs.sliceItems(rs.ptrSlice(rs.ptrAdd(end, -len), rs.usizeAdd(len, k)))) { - put$spare_Item_String(_71, out); + put$spare_Item_Vec_u8(_71, out); } - rs.vecSetLen(moved, rs.usizeAdd(len, k), 12, (): rs.String => { - return (undefined as unknown) as rs.String; + rs.vecSetLen(moved, rs.usizeAdd(len, k), 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; }); v = moved; break b2; } if (_35 === 1) { - rs.ptrCopy(rs.vecAsPtr(rs.iterToVec(rs.iterMap(new rs.Range(0, k), (i5: rs.usize): rs.String => { - return of$spare_Item_String(n ^ (i5 & 0xff)); - }), undefined, 12, true), 12), rs.ptrAdd(rs.vecAsPtr(v, 12), len), k, copy$String); - rs.vecSetLen(v, rs.usizeAdd(len, k), 12, (): rs.String => { - return (undefined as unknown) as rs.String; + rs.ptrCopy(rs.vecAsPtr(rs.iterToVec(rs.iterMap(new rs.Range(0, k), (i5: rs.usize): rs.ByteVec => { + return of$spare_Item_Vec_u8(n ^ (i5 & 0xff)); + }), undefined, 12, true), 12), rs.ptrAdd(rs.vecAsPtr(v, 12), len), k, copy$Vec_u8); + rs.vecSetLen(v, rs.usizeAdd(len, k), 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; }); break b2; } if (_35 === 2) { - const iter3: rs.Iter<[rs.usize, rs.Place]> = rs.iterRev(rs.iterEnumerate(rs.iterSliceMut(rs.ptrSlice(rs.ptrAdd(rs.vecAsPtr(v, 12), len), k)))); + const iter3: rs.Iter<[rs.usize, rs.Place]> = rs.iterRev(rs.iterEnumerate(rs.iterSliceMut(rs.ptrSlice(rs.ptrAdd(rs.vecAsPtr(v, 12), len), k)))); while (true) { - const _108: [rs.usize, rs.Place] | rs.Done = iter3.next(); + const _108: [rs.usize, rs.Place] | rs.Done = iter3.next(); if (_108 === rs.DONE) { break; } - _108[1].v = of$spare_Item_String(rs.u8WrappingSub(n, _108[0] & 0xff)); + _108[1].v = of$spare_Item_Vec_u8(rs.u8WrappingSub(n, _108[0] & 0xff)); } - rs.vecSetLen(v, rs.usizeAdd(len, k), 12, (): rs.String => { - return (undefined as unknown) as rs.String; + rs.vecSetLen(v, rs.usizeAdd(len, k), 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; }); break b2; } if (_35 === 3) { rs.vecReserve(v, 1, 12); const at: rs.usize = rs.usizeRem(n as rs.usize, rs.usizeAdd(len, 1)); - const p: rs.Place = rs.ptrAdd(rs.vecAsPtr(v, 12), at); - rs.ptrCopy(p, rs.ptrAdd(p, 1), rs.usizeSub(len, at), copy$String); - p.v = of$spare_Item_String(n); - rs.vecSetLen(v, rs.usizeAdd(len, 1), 12, (): rs.String => { - return (undefined as unknown) as rs.String; + const p: rs.Place = rs.ptrAdd(rs.vecAsPtr(v, 12), at); + rs.ptrCopy(p, rs.ptrAdd(p, 1), rs.usizeSub(len, at), copy$Vec_u8); + p.v = of$spare_Item_Vec_u8(n); + rs.vecSetLen(v, rs.usizeAdd(len, 1), 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; }); break b2; } @@ -400,40 +432,40 @@ export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { if (len > 0) { const at2: rs.usize = rs.usizeRem(n as rs.usize, len); const end2: rs.usize = rs.usizeAdd(at2, rs.usizeRem(n >> 4 as rs.usize, rs.usizeAdd(rs.usizeSub(len, at2), 1))); - rs.vecSetLen(v, at2, 12, (): rs.String => { - return (undefined as unknown) as rs.String; + rs.vecSetLen(v, at2, 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; }); - const p2: rs.Place = rs.vecAsPtr(v, 12); + const p2: rs.Place = rs.vecAsPtr(v, 12); let iterStart2: rs.usize = at2; const iterEnd2: rs.usize = end2; while ((_165 = iterStart2++) < iterEnd2) { - put$spare_Item_String(copy$String(rs.ptrAdd(p2, _165).v), out); + put$spare_Item_Vec_u8(copy$Vec_u8(rs.ptrAdd(p2, _165).v), out); } - rs.ptrCopy(rs.ptrAdd(p2, end2), rs.ptrAdd(p2, at2), rs.usizeSub(len, end2), copy$String); - rs.vecSetLen(v, rs.usizeSub(len, rs.usizeSub(end2, at2)), 12, (): rs.String => { - return (undefined as unknown) as rs.String; + rs.ptrCopy(rs.ptrAdd(p2, end2), rs.ptrAdd(p2, at2), rs.usizeSub(len, end2), copy$Vec_u8); + rs.vecSetLen(v, rs.usizeSub(len, rs.usizeSub(end2, at2)), 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; }); break b2; } } else { if (_35 === 5) { - rs.vecSetLen(v, 0, 12, (): rs.String => { - return (undefined as unknown) as rs.String; + rs.vecSetLen(v, 0, 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; }); - const p3: rs.Place = rs.vecAsPtr(v, 12); + const p3: rs.Place = rs.vecAsPtr(v, 12); let kept: rs.usize = 0; let iterStart3: rs.usize = 0; const iterEnd3: rs.usize = len; while ((_194 = iterStart3++) < iterEnd3) { const i4: rs.usize = _194; - const x: rs.String = copy$String(rs.ptrAdd(p3, i4).v); + const x: rs.ByteVec = copy$Vec_u8(rs.ptrAdd(p3, i4).v); if (rs.usizeRem(rs.usizeAdd(i4, n as rs.usize), 3) !== 0) { rs.ptrAdd(p3, kept).v = x; kept = rs.usizeAdd(kept, 1); } } - rs.vecSetLen(v, kept, 12, (): rs.String => { - return (undefined as unknown) as rs.String; + rs.vecSetLen(v, kept, 12, (): rs.ByteVec => { + return (undefined as unknown) as rs.ByteVec; }); break b2; } @@ -444,7 +476,7 @@ export function pointers$String(data: rs.ByteSlice, out: rs.ByteVec): void { } rs.vecTruncate(v, rs.usizeRem(n as rs.usize, rs.usizeAdd(len, 1))); } - state$String(v, out); + state$Vec_u8(v, out); } } @@ -1161,7 +1193,7 @@ export function spares(kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): void { if (_8 === 0) { pointers$u32(data, out); } else if (_8 === 1) { - pointers$String(data, out); + pointers$Vec_u8(data, out); } else if (_8 === 2) { pointers$Option_u16(data, out); } else if (_8 === 3) { diff --git a/ts/fixtures/lang/elements/src/spare.rs b/ts/fixtures/lang/elements/src/spare.rs index 3faaf2765..4408ccd8c 100644 --- a/ts/fixtures/lang/elements/src/spare.rs +++ b/ts/fixtures/lang/elements/src/spare.rs @@ -3,8 +3,8 @@ //! through the slots `spare_capacity_mut` lends, then taken in by `set_len`; shortened by //! `set_len` and read back past its length, as std's `Drain` and `retain` read theirs, then //! lengthened over what it gave up; and slots never written, copied as they are. Of numbers, -//! `String`s, `Option`s, values that count their drops, and values JS keeps as objects, by -//! a generic function too. +//! bytes, `String`s, `Option`s, values that count their drops, and values JS keeps as +//! objects, by a generic function too. use std::mem::MaybeUninit; use std::ptr; @@ -38,6 +38,18 @@ impl Item for String { } } +/// Bytes, whose length JS keeps apart from them, as Rust does, so a pointer moves one exactly. +impl Item for Vec { + fn of(n: u8) -> Self { + vec![n; usize::from(n % 3)] + } + + fn put(&self, out: &mut Vec) { + out.extend_from_slice(self); + out.push(0xff); + } +} + impl Item for Option { fn of(n: u8) -> Self { (n % 3 != 0).then_some(u16::from(n) << 4) @@ -301,7 +313,8 @@ pub fn spares(kind: u8, data: &[u8], out: &mut Vec) { DROPS.store(0, Ordering::Relaxed); match kind % 10 { 0 => pointers::(data, out), - 1 => pointers::(data, out), + // A `String`'s JS object keeps its length with its text, so a pointer can't move one. + 1 => pointers::>(data, out), 2 => pointers::>(data, out), 3 => slots::(data, out), 4 => slots::(data, out), diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 079e84442..0a012ab15 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -6,8 +6,8 @@ fixtures/reject/src/alloc.rs:alloc_retyped+8:21: memory from `alloc` cast to poi fixtures/reject/src/alloc.rs:alloc_retyped+10:17: memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:allocated+4:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as fixtures/reject/src/alloc.rs:allocated+5:11: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as -fixtures/reject/src/alloc.rs:beside_reference+2:14: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust -fixtures/reject/src/alloc.rs:beside_reference+2:35: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:beside_reference+2:11: a read or write reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:beside_reference+2:32: a read or write reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:beside_reference+2:44: a slice of a length the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:bytes_as_values+3:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index cf3a07a2d..e70a4c0a2 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -10,8 +10,8 @@ fixtures/reject/src/alloc.rs:alloc_retyped+8:21: memory from `alloc` cast to poi fixtures/reject/src/alloc.rs:alloc_retyped+10:17: memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:allocated+4:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as fixtures/reject/src/alloc.rs:allocated+5:11: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as -fixtures/reject/src/alloc.rs:beside_reference+2:14: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust -fixtures/reject/src/alloc.rs:beside_reference+2:35: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:beside_reference+2:11: a read or write reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/alloc.rs:beside_reference+2:32: a read or write reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:beside_reference+2:44: a slice of a length the body doesn't fix, from a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/alloc.rs:bytes_as_values+3:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as fixtures/reject/src/alloc.rs:bytes_as_values+4:15: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 4b6fcd2bf..61ad7f9c2 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -65,9 +65,9 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { /// Why the call of std's `def_id` would fail at run time where Rust's is defined, if the /// body proves it would: `offset_from` between pointers JS keeps in different arrays, or /// a pointer, slice, copy, or write reaching out of an array of one or a copied byte. - /// From an array of one, every offset, count, and length must be one the body fixes and - /// that stays within it, a pointer still at its element: past it, Rust may reach the - /// neighbour the reference was borrowed beside, which no body here shows. + /// From an array of one, every offset, count, length, and access, wrapping or not, must + /// be one the body fixes and proves stays within it: past it, Rust may reach the + /// neighbour the reference was borrowed beside, which JS keeps apart. pub(super) fn stray( &self, def_id: rustc_hir::def_id::DefId, @@ -91,33 +91,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { { return Some(super::alloc::BYTES.into()); } - // An offset or a count the body doesn't fix may reach out of an array of one. - let unfixed = |o: Origin<'tcx>, what: &str| { - matches!(o.array, Array::Lone(_)).then(|| { - format!( - "{what} the body doesn't fix, from {}, which JS keeps apart from what holds it in Rust", - o.array.what() - ) - }) - }; - let out = |o: Origin<'tcx>, n: Option, what: &str| { - let Some(index) = o.index else { - return unfixed(o, &format!("{what} moved by an offset")); - }; - // A pointer from an array of one stays at its element: one past it is Rust's - // too, but a read through it, which reaches the neighbour, is where no check is. - let n = match o.array { - Array::Lone(_) => n.or(Some(1)), - _ => n, - }; - let past = o.array.len().is_some_and(|len| index + n.unwrap_or(0) > len); - (index < 0 || past).then(|| { - format!( - "{what} reaching past {}, which JS keeps apart from what holds it in Rust", - o.array.what() - ) - }) - }; + let (unfixed, out) = (Self::unfixed, Self::out); match (owner.as_str(), name.as_str()) { ("ptr" | "NonNull", "cast") => self.erased_borrow(arg(0)?, pointee, gargs.types().nth(1)?), ("Arc" | "Rc", "from_raw" | "increment_strong_count" | "decrement_strong_count") @@ -148,10 +122,27 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } // A wrapping offset may leave any other array while nothing reads through it. ("ptr" | "NonNull", "wrapping_add" | "wrapping_sub" | "wrapping_offset") => { - let o = self.origin(arg(0)?)?; - let lone = matches!(o.array, Array::Lone(_)); - lone.then(|| out(self.moved(o, name.as_str(), arg(1)?), None, "a pointer"))? + let o = self.lone_origin(arg(0)?)?; + out(self.moved(o, name.as_str(), arg(1)?), None, "a pointer") } + // An access through a pointer, which the body shows one element at. + ( + "ptr" | "NonNull", + "read" + | "read_unaligned" + | "read_volatile" + | "write" + | "write_unaligned" + | "write_volatile" + | "replace" + | "swap" + | "as_ref" + | "as_mut" + | "drop_in_place", + ) => [arg(0), arg(1)] + .into_iter() + .flatten() + .find_map(|p| self.access(self.lone_origin(p)?)), ("raw", "from_raw_parts" | "from_raw_parts_mut") | ("ptr", "slice_from_raw_parts" | "slice_from_raw_parts_mut") | ("NonNull", "slice_from_raw_parts") => { @@ -196,6 +187,56 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } } + /// Why a read or write through the raw pointer `place` derefs would fail at run time, if + /// the body shows that pointer off the element of an array of one. + pub(super) fn beside(&self, place: mir::Place<'tcx>) -> Option { + let [ProjectionElem::Deref, ..] = place.projection.as_slice() else { + return None; + }; + if !self.mir.local_decls[place.local].ty.is_raw_ptr() { + return None; + } + self.access(self.lone_origin(&Operand::Copy(mir::Place::from(place.local)))?) + } + + /// Why an access of the one element a pointer at `o` reads or writes reaches out of its + /// array, if the body fixes where it is: one past it, as a pointer further out, or at + /// an offset the body doesn't fix, is refused where it is made. + fn access(&self, o: Origin<'tcx>) -> Option { + let len = o.array.len()?; + (o.index? == len).then(|| Self::out(o, Some(1), "a read or write"))? + } + + /// Where the pointer `op` points, if the body shows it in an array of one. + fn lone_origin(&self, op: &Operand<'tcx>) -> Option> { + self.origin(op).filter(|o| matches!(o.array, Array::Lone(_))) + } + + /// Why an offset or a count the body doesn't fix, from `o`, may reach out of it: only + /// from an array of one. + fn unfixed(o: Origin<'tcx>, what: &str) -> Option { + matches!(o.array, Array::Lone(_)).then(|| { + format!( + "{what} the body doesn't fix, from {}, which JS keeps apart from what holds it in Rust", + o.array.what() + ) + }) + } + + /// Why `what`, at `o` and reaching `n` elements past it if a count, leaves `o`'s array. + fn out(o: Origin<'tcx>, n: Option, what: &str) -> Option { + let Some(index) = o.index else { + return Self::unfixed(o, &format!("{what} moved by an offset")); + }; + let past = o.array.len().is_some_and(|len| index + n.unwrap_or(0) > len); + (index < 0 || past).then(|| { + format!( + "{what} reaching past {}, which JS keeps apart from what holds it in Rust", + o.array.what() + ) + }) + } + /// Whether JS keeps `a` and `b` in different arrays. Arrays of different variables are; /// of one variable's, those at different fields or constant indices are, where Rust's /// are one allocation. @@ -265,6 +306,10 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { ("ptr" | "NonNull", "add" | "sub" | "offset") => { Some(self.moved(self.origin(&args[0].node)?, name.as_str(), &args[1].node)) } + // Only an array of one's, which every wrapping offset must stay within. + ("ptr" | "NonNull", "wrapping_add" | "wrapping_sub" | "wrapping_offset") => { + Some(self.moved(self.lone_origin(&args[0].node)?, name.as_str(), &args[1].node)) + } ("NonNull", "as_ptr" | "new_unchecked") => self.origin(&args[0].node), ("slice" | "Vec", "as_ptr" | "as_mut_ptr") => self.start(&args[0].node), // The start of its own text's encoding, though a substring's bytes start past its diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 7818dca0b..b76111e19 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -64,7 +64,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { /// The JS expression of `place`, read without a copy. pub fn place_expr(&mut self, place: Place<'tcx>, span: Span, out: &mut Vec) -> Expr { let tcx = self.tcx; - if let Some(why) = self.alloc_bytes(place) { + if let Some(why) = self.alloc_bytes(place).map(String::from).or_else(|| self.beside(place)) { return self.err(span, why); } // A local held as its parts reads them, as far as they go. @@ -827,7 +827,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { pub(super) fn store(&mut self, place: Place<'tcx>, value: Expr, span: Span) -> Vec { let (place, value) = self.rebuilt(place, value, span); let mut out = Vec::new(); - if let Some(why) = self.alloc_bytes(place) { + if let Some(why) = self.alloc_bytes(place).map(String::from).or_else(|| self.beside(place)) { out.push(StmtKind::Expr(self.err(span, why)).at(None)); return out; } From 521d90137251122843ca344188d99a6fa14d7a6a Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 12:42:58 -0800 Subject: [PATCH 28/29] style(ts): format Co-Authored-By: Claude Opus 5.5 --- ts/src/lower/mir/origin.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 61ad7f9c2..e80472dfe 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -128,17 +128,8 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { // An access through a pointer, which the body shows one element at. ( "ptr" | "NonNull", - "read" - | "read_unaligned" - | "read_volatile" - | "write" - | "write_unaligned" - | "write_volatile" - | "replace" - | "swap" - | "as_ref" - | "as_mut" - | "drop_in_place", + "read" | "read_unaligned" | "read_volatile" | "write" | "write_unaligned" | "write_volatile" + | "replace" | "swap" | "as_ref" | "as_mut" | "drop_in_place", ) => [arg(0), arg(1)] .into_iter() .flatten() From 8ca09a4fb7ad9d15fa3174184e6c857c07453db6 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 13:07:23 -0800 Subject: [PATCH 29/29] fix(ts): check only accessed pointers, and none to a value of no bytes Co-Authored-By: Claude Opus 5.5 --- .../pointers/expected/pointers.Nothing.ts | 7 +++++++ .../lang/pointers/expected/pointers.ts | 13 ++++++++++++ ts/fixtures/lang/pointers/src/pointers.rs | 20 +++++++++++++++++++ ts/src/lower/mir/origin.rs | 11 +++++++--- 4 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 ts/fixtures/lang/pointers/expected/pointers.Nothing.ts diff --git a/ts/fixtures/lang/pointers/expected/pointers.Nothing.ts b/ts/fixtures/lang/pointers/expected/pointers.Nothing.ts new file mode 100644 index 000000000..eb46c21cc --- /dev/null +++ b/ts/fixtures/lang/pointers/expected/pointers.Nothing.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** A value of no bytes, whose every pointer is where it was. */ +export interface Nothing { +} + +//# sourceMappingURL=pointers.Nothing.ts.map diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 912806b0d..9f1762893 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -29,6 +29,7 @@ import type * as Loaded from "./pointers.Loaded.ts"; import type * as Log from "./pointers.Log.ts"; import * as Named from "./pointers.Named.ts"; import type * as Node from "./pointers.Node.ts"; +import type * as Nothing from "./pointers.Nothing.ts"; import type * as Outer from "./pointers.Outer.ts"; import type * as Pair from "./pointers.Pair.ts"; import * as Paired from "./pointers.Paired.ts"; @@ -3429,10 +3430,22 @@ export function behindRefs(data: rs.ByteSlice, out: rs.ByteVec): void { rs.byteVecPush(out, q.v & 0xff); rs.byteVecPush(out, rs.ptrOffsetFrom(rs.arcRaw(arc2, rs.arcPlace(arc2), "Arc"), p) & 0xff); rs.byteVecPush(out, from$convert_From_ref_pointers_Halves32_ptr_NonNull_u32({ first: bits as rs.u32, second: ((bits as rs.u32) ^ 85) >>> 0 }).v & 0xff); + let lone: rs.u32 = bits as rs.u32; + const at: rs.Place = rs.place(() => lone, (value) => lone = value); + let end: rs.Place = rs.NULL; + end = rs.ptrAdd(at, 1); + rs.byteVecPush(out, rs.ptrOffsetFrom(end, at) & 0xff); + const _282: Nothing.Nothing = {}; + rs.byteVecPush(out, nothingTag(_282, bits)); arc2[Symbol.dispose](); arc[Symbol.dispose](); } +/** `bits`, given beside a `Nothing` borrowed through a pointer moved off one. */ +export function nothingTag(_nothing: Nothing.Nothing, bits: rs.u8): rs.u8 { + return bits ^ 60; +} + export function from$convert_From_ref_pointers_Halves32_ptr_NonNull_u32(halves: Halves32.Halves32): rs.Place { return rs.place(() => halves.second, (value) => halves.second = value); } diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index c3ddb6092..9642a37f0 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2656,6 +2656,26 @@ pub fn behind_refs(data: &[u8], out: &mut Vec) { }; let second: NonNull = (&halves).into(); out.push(unsafe { *second.as_ptr() } as u8); + // A pointer one past a variable, stored whole through a pointer to a pointer, and one + // moved off a value of no bytes and borrowed: valid, as neither reads past its value. + let lone = u32::from(bits); + let at = &raw const lone; + let mut end = ptr::null::(); + unsafe { ptr::write(&raw mut end, at.add(1)) }; + out.push(unsafe { end.offset_from(at) } as u8); + let none = Nothing; + let past = unsafe { (&raw const none).add(1) }; + out.push(nothing_tag(unsafe { &*past }, bits)); +} + +/// A value of no bytes, whose every pointer is where it was. +struct Nothing; + +/// `bits`, given beside a `Nothing` borrowed through a pointer moved off one. +#[inline(never)] +fn nothing_tag(nothing: &Nothing, bits: u8) -> u8 { + let Nothing = nothing; + bits ^ 0x3c } /// Two numbers, the second of which a pointer from a reference to them points at. diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index e80472dfe..b1467b132 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -125,12 +125,13 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { let o = self.lone_origin(arg(0)?)?; out(self.moved(o, name.as_str(), arg(1)?), None, "a pointer") } - // An access through a pointer, which the body shows one element at. + // An access through a pointer, which the body shows one element at: both of a + // swap's, and else the first argument, as the second is the value written. ( "ptr" | "NonNull", "read" | "read_unaligned" | "read_volatile" | "write" | "write_unaligned" | "write_volatile" | "replace" | "swap" | "as_ref" | "as_mut" | "drop_in_place", - ) => [arg(0), arg(1)] + ) => [Some(arg(0)?), arg(1).filter(|_| name == "swap")] .into_iter() .flatten() .find_map(|p| self.access(self.lone_origin(p)?)), @@ -184,7 +185,11 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { let [ProjectionElem::Deref, ..] = place.projection.as_slice() else { return None; }; - if !self.mir.local_decls[place.local].ty.is_raw_ptr() { + // Every pointer to a value of no bytes is where it was, as `stray` leaves it. + let ty::RawPtr(pointee, _) = *self.mir.local_decls[place.local].ty.kind() else { + return None; + }; + if self.b.t.zero_sized(pointee) || self.b.t.pointee(pointee) != Pointee::Place { return None; } self.access(self.lone_origin(&Operand::Copy(mir::Place::from(place.local)))?)