diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index 95dddf56c..1c5fde132 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -21,6 +21,22 @@ The codec's differential harness is the pattern to repeat. The shared npm runtim (for checked integer ops and the rest of `rs.ts`) is published only once two generated packages need it. +✅ Decided with the maintainer on 2026-11-04, for any crate: a pointer's address read as an +integer is an id unique among live objects, a multiple of the type's alignment, and whatever +has no exact answer, such as a pointer made back from an address, is refused at translation. + +✅ Decided with the maintainer on 2026-11-06, refining that: only an `Arc`'s or `Rc`'s value +has an address. Its id belongs to the `rs.Arc` allocation and is freed for reuse when its +strong and weak counts reach 0, exactly as Rust frees it. Reading the address of a type the +program keeps anywhere else (a borrowed local, a field, a `Box`, a collection, a static), and +moving one out of its `Arc` (`into_inner`, `try_unwrap`, `unwrap_or_clone`, `make_mut`, a +whole write through `get_mut`), is refused with a span. Rejected: freeing an id when JS +collects its object, since GC never runs within one synchronous job, so churning +allocations could exhaust wasm32's 4 GiB where Rust reuses the freed memory; freeing at each +type's drop, which changes drop emission and still refuses values never dropped; and a +counter that never reuses, since `usize` is 32 bits on wasm32, so a long-running program +would exhaust the address space where Rust reuses freed blocks. + Decided with the maintainer on 2026-10-23, for the subset: `u128` stays, on the [wide-integer](/ts/README.md#design) representation. @@ -45,12 +61,12 @@ Decided with the maintainer on 2026-10-27, from a census of the model's 22 publi - The end-to-end work stays here: the model's roots, its differential harness, and the `frames.ts` workload. moq writes the binding files `model.toml` names, in its own tree. -The census at the pin on 2026-11-06: 73 refusals. `Timestamp` alone refuses nothing, and +The census at the pin on 2026-11-06: 66 refusals. `Timestamp` alone refuses nothing, and the [model fixture](/ts/README.md#tests)'s harness runs it against native moq-net at the pin; each case it adds as a type translates. Every other root reaches the rest of the model, so each child below unblocks all of them together. The rest is moq's, for its research quest: kio's `log.rs` reads bytes as its segments and retires them through -crossbeam-epoch, whose tagged pointers and inline closures (36 refusals) translation +crossbeam-epoch, whose tagged pointers and inline closures (30 refusals) translation refuses for good, and smallvec's union over a type parameter and raw parts (10) wait on [unsafe](/quest/m3/ts-unsafe/README.md), so both need safe `cfg(rust2_ts)` twins or bindings there, as does the `unsafe` slice of `model/frame.rs`'s `Filled`. @@ -63,7 +79,6 @@ kept separate since each lands alone; rejected: one quest for `ArrayString`. ## Required - [Bytes from an owner that drops](/quest/m2/ts-model/bytes-owner.md) - `Bytes::from_owner` of a frame's segment -- [Addresses as identity](/quest/m2/ts-model/addresses.md) - `Arc::as_ptr(a) as usize` as a key, which needs a decision - [Option fields at a unit instance](/quest/m2/ts-model/unit-option-fields.md) - `Idle<()>`'s `Option` field tells `Some(())` from `None` - [panic! in a 2015 or 2018 crate](/quest/m2/ts-model/begin-panic.md) - the `begin_panic` mapping keyed by its defining module - [A string's bytes through its pointer](/quest/m2/ts-model/str-as-ptr.md) - `str::as_ptr` reads the string's UTF-8 diff --git a/quest/m2/ts-model/addresses.md b/quest/m2/ts-model/addresses.md deleted file mode 100644 index f78036cfb..000000000 --- a/quest/m2/ts-model/addresses.md +++ /dev/null @@ -1,24 +0,0 @@ -# [M] Addresses as identity - -## Goal - -[rust2-ts](/ts/README.md) translates a pointer's address read as an integer where a crate -uses it only as an identity: compared, hashed, or ordered as a key, such as -`Arc::as_ptr(a) as usize` and `Weak::as_ptr(w) as usize`, proven by differential tests -against native Rust. Today each pointer-to-integer cast is refused. - -## Plan - -moq-net's model keys its cache and frame accounting by address six times at the pin -(`model/cache.rs`, `model/frame.rs`, `model/group/content.rs`). - -Open question for the maintainer, since the 2026-10-28 decision refuses pointer-to-integer -casts: - -1. An address is an id unique among live objects, assigned on first read and kept in a - `WeakMap`, a multiple of the type's alignment so alignment checks hold, while an - integer cast back to a pointer stays refused (recommended: it closes the common - identity idiom for every crate, and Rust promises nothing more of an address's value). -2. Keep the refusal, and moq gives these a `cfg(rust2_ts)` twin keyed by an id of its own. - -Either way, crossbeam-epoch's tagged pointers stay refused: they cast the integer back. diff --git a/quest/m3/ts-runtime-throws.md b/quest/m3/ts-runtime-throws.md index 7cac8d006..06b2cb728 100644 --- a/quest/m3/ts-runtime-throws.md +++ b/quest/m3/ts-runtime-throws.md @@ -30,7 +30,7 @@ owns, or a pointer kept in a field), `Bytes::slice_ref` of other storage, `alloc values, ranges stepped without their type, and fmt widths written as a placeholder. `git grep 'rust2-ts: ' ts/runtime` lists them; classify each before fixing. The pointer comparisons [#584](https://github.com/moq-dev/rust2/pull/584) refuses at translation are -in scope too, through [addresses](/quest/m2/ts-model/addresses.md)' identities. +in scope too. Public API: the translator's output, and which crates translate. diff --git a/ts/README.md b/ts/README.md index d7717fd16..840afcb6f 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,6 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `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 | +| a raw pointer's address: `p as usize`, `addr`, `expose_provenance`, and a raw pointer's `Hash` and `Ord` | `rs.addr`: null's 0, a dangling pointer's alignment, `Weak::new`'s `usize::MAX`, and otherwise the address of the `Arc` or `Rc` value it points at, as an allocator places the allocation: after its counts, at a multiple of the value's alignment, in a block no other live allocation's overlaps, freed for reuse once its strong and weak counts reach 0, as Rust frees it. Only a value an `Arc` or `Rc` holds itself has one: an address of a type the program keeps anywhere else, such as in a local it borrows, drops, or gives std, a field, a `Box`, a collection, a static, or what JS is given or gives the crate whole, or writes whole in place, as through `get_mut`, is refused, as are moving one out of its `Arc` by `into_inner` or `make_mut` (`try_unwrap` and `unwrap_or_clone` translate for no type), `Weak::as_ptr` of one `Arc::new_cyclic` makes, and a pointer made of an address | | `MaybeUninit` | `T`: `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 | @@ -1079,7 +1080,7 @@ they need. ## Not yet -Byte reinterpretation, pointer addresses, pointer arithmetic on a value JS keeps as an object (other than wrapping arithmetic on a null or dangling pointer), memory from `alloc` other than an array of values JS copies, and unions whose +Byte reinterpretation, pointers made of addresses, pointer arithmetic on a value JS keeps as an object (other than wrapping arithmetic on a null or dangling pointer), memory from `alloc` other than an array of values JS copies, and unions whose fields overlap in different types ([unsafe](/quest/m3/ts-unsafe/README.md)), and trait objects other than `dyn Debug`, `dyn Display`, `dyn Future`, `dyn Any`, `dyn Error`, `dyn Fn*`, `dyn Buf`, `dyn Iterator` and its subtraits, and the translated crates' traits' behind `&`, `&mut`, `Box`, `Arc`, `Rc`, `Weak`, or `Pin` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `Box` made `From` a string, binds to diff --git a/ts/fixtures/census/census.md b/ts/fixtures/census/census.md index e7587fd63..2bdba0018 100644 --- a/ts/fixtures/census/census.md +++ b/ts/fixtures/census/census.md @@ -9,11 +9,12 @@ | Count | Construct | First | | ---: | --- | --- | | 3 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | fixtures/census/src/lib.rs:9:2 | -| 2 | a cast from `_` to `_`, between a pointer and an integer | fixtures/census/src/lib.rs:9:2 | | 2 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | fixtures/census/src/lib.rs:19:12 | | 2 | a mutable static, which any code may write | fixtures/census/src/lib.rs:38:3 | | 1 | `Bytes::from_owner` of `_`, which would drop with the last clone of what it makes, which is not supported | fixtures/census/src/lib.rs:45:2 | | 1 | `Pointer` formatting, as in `{:p}`, is not supported | fixtures/census/src/lib.rs:24:2 | +| 1 | a pointer made of an address, which JS has no memory at | fixtures/census/src/lib.rs:14:2 | +| 1 | the address of a pointer to `_`, each a place JS makes of its own | fixtures/census/src/lib.rs:9:2 | | 1 | union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart | fixtures/census/src/lib.rs:28:1 | ## By API @@ -22,12 +23,9 @@ The type or module each refused type or path belongs to. | Count | API | First | | ---: | --- | --- | -| 4 | `u32` | fixtures/census/src/lib.rs:9:2 | +| 5 | `u32` | fixtures/census/src/lib.rs:9:2 | | 2 | `u64` | fixtures/census/src/lib.rs:19:47 | | 2 | `u8` | fixtures/census/src/lib.rs:19:12 | -| 2 | `usize` | fixtures/census/src/lib.rs:9:2 | -| 1 | `*const T` | fixtures/census/src/lib.rs:14:2 | -| 1 | `*const u32` | fixtures/census/src/lib.rs:9:2 | | 1 | `Bits` | fixtures/census/src/lib.rs:28:1 | | 1 | `std::sync::Arc` | fixtures/census/src/lib.rs:45:2 | @@ -37,11 +35,10 @@ The type or module each refused type or path belongs to. | ---: | --- | --- | --- | | 2 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | `u32` | fixtures/census/src/lib.rs:9:2 | | 1 | `Bytes::from_owner` of `_`, which would drop with the last clone of what it makes, which is not supported | `std::sync::Arc` | fixtures/census/src/lib.rs:45:2 | -| 1 | a cast from `_` to `_`, between a pointer and an integer | `*const T`, `usize` | fixtures/census/src/lib.rs:14:2 | -| 1 | a cast from `_` to `_`, between a pointer and an integer | `*const u32`, `usize` | fixtures/census/src/lib.rs:9:2 | | 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `u8` | fixtures/census/src/lib.rs:19:12 | | 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u64`, `u8` | fixtures/census/src/lib.rs:19:47 | | 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | `u64` | fixtures/census/src/lib.rs:19:48 | +| 1 | the address of a pointer to `_`, each a place JS makes of its own | `u32` | fixtures/census/src/lib.rs:9:2 | | 1 | union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart | `Bits`, `u32` | fixtures/census/src/lib.rs:28:1 | ## Spans @@ -54,13 +51,6 @@ The type or module each refused type or path belongs to. -
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 -- fixtures/census/src/lib.rs:14:2: a cast from `*const T` to `usize`, between a pointer and an integer - -
-
a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another (2) - fixtures/census/src/lib.rs:19:12: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another @@ -87,6 +77,18 @@ The type or module each refused type or path belongs to.
+
a pointer made of an address, which JS has no memory at (1) + +- fixtures/census/src/lib.rs:14:2: a pointer made of an address, which JS has no memory at + +
+ +
the address of a pointer to `_`, each a place JS makes of its own (1) + +- fixtures/census/src/lib.rs:9:2: the address of a pointer to `u32`, each a place JS makes of its own + +
+
union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart (1) - fixtures/census/src/lib.rs:28:1: union `Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart diff --git a/ts/fixtures/census/src/lib.rs b/ts/fixtures/census/src/lib.rs index b4037ce73..29343aedd 100644 --- a/ts/fixtures/census/src/lib.rs +++ b/ts/fixtures/census/src/lib.rs @@ -4,14 +4,14 @@ #![allow(dead_code, reason = "each item is a census root, not called from Rust")] -/// A pointer's address, which JS has no counterpart of. +/// The address of a place, which JS makes anew each borrow. pub fn addressed(n: u32) -> usize { &n as *const u32 as usize } -/// A pointer's address in a body no instance reaches, lowered over `T`. -pub fn generic(t: T) -> usize { - &t as *const T as usize +/// A pointer made of an address in a body no instance reaches, lowered over `T`. +pub fn generic(at: usize) -> *const T { + std::ptr::without_provenance(at) } /// A pointer read as another type's bytes, at two types. diff --git a/ts/fixtures/lang/pointers/expected/core/option.ts b/ts/fixtures/lang/pointers/expected/core/option.ts index 8f6edbacb..b3ed0c268 100644 --- a/ts/fixtures/lang/pointers/expected/core/option.ts +++ b/ts/fixtures/lang/pointers/expected/core/option.ts @@ -2,6 +2,7 @@ import * as rs from "../rs.ts"; import { copy$pointers_Pair, copy$pointers_Spot } from "../lib.ts"; +import type * as Keyed from "../pointers.Keyed.ts"; import type * as Loaded from "../pointers.Loaded.ts"; import type * as Pair from "../pointers.Pair.ts"; import type * as Spot from "../pointers.Spot.ts"; @@ -13,6 +14,13 @@ export function isSomeAnd$1s2u5lz(self: rs.u8 | undefined, f: (a0: rs.u8) => boo return f(self); } +export function isSomeAnd$0fp5rqa(self: rs.Arc | undefined, f: (a0: rs.Arc) => boolean): boolean { + if (self === undefined) { + return false; + } + return f(self); +} + export function isSomeAnd$1nosv3e(self: [rs.Arc<[rs.u16, rs.u8] | undefined>, rs.Arc] | undefined, f: (a0: [rs.Arc<[rs.u16, rs.u8] | undefined>, rs.Arc]) => boolean): boolean { if (self === undefined) { return false; diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index e38684560..553a48626 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, addresses, arrays } from "./pointers.ts"; import type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; import type * as ByCall from "./pointers.ByCall.ts"; @@ -11,6 +11,7 @@ import type * as Duo from "./pointers.Duo.ts"; import type * as Halves from "./pointers.Halves.ts"; import type * as Housed from "./pointers.Housed.ts"; import type * as Inner from "./pointers.Inner.ts"; +import type * as Keyed from "./pointers.Keyed.ts"; import type * as Leaning from "./pointers.Leaning.ts"; import * as List from "./pointers.List.ts"; import type * as Outer from "./pointers.Outer.ts"; @@ -201,6 +202,10 @@ export function drop$tuple2_sync_Arc_pointers_Twin_Rc_pointers_Twin(_1: [rs.Arc< _1[1][Symbol.dispose](); } +export function drop$tuple2_sync_Weak_pointers_Keyed_usize(_1: [rs.Weak, rs.usize]): void { + _1[0][Symbol.dispose](); +} + export function drop$tuple2_u8_sync_Arc_u8(_1: [rs.u8, rs.Arc]): void { _1[1][Symbol.dispose](); } @@ -287,8 +292,10 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By rawArcs(data25, out25); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::raw_places", (_235: rs.u8, data26: rs.ByteSlice, out26: rs.ByteVec): undefined => { rawPlaces(data26, out26); - }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::arrays", (_236: rs.u8, data27: rs.ByteSlice, out27: rs.ByteVec): undefined => { - arrays(data27, out27); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::addresses", (_236: rs.u8, data27: rs.ByteSlice, out27: rs.ByteVec): undefined => { + addresses(data27, out27); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::arrays", (_237: rs.u8, data28: rs.ByteSlice, out28: rs.ByteVec): undefined => { + arrays(data28, out28); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/pointers/expected/pointers.Keyed.ts b/ts/fixtures/lang/pointers/expected/pointers.Keyed.ts new file mode 100644 index 000000000..093768b7b --- /dev/null +++ b/ts/fixtures/lang/pointers/expected/pointers.Keyed.ts @@ -0,0 +1,42 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** + * A value keyed by its address in a registry it leaves as it drops, as a cache keys + * what it tracks: by `Arc::as_ptr` as it is made, and by `ptr::from_ref` and a cast of + * `self` as it drops, which must be the same address. + */ +export class Keyed { + id: rs.u8; + registry: rs.Arc>>; + left: rs.Arc>; + + constructor(id: rs.u8, registry: rs.Arc>>, left: rs.Arc>) { + this.id = id; + this.registry = registry; + this.left = left; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + this.registry[Symbol.dispose](); + this.left[Symbol.dispose](); + } +} + +export function drop$ops_Drop(self: Keyed): void { + const address: rs.usize = rs.addr(self, 12, 4); + const _8: rs.RefCell> = rs.refCellBorrowMut(self.registry.value); + const _12: [rs.u8, rs.usize] = [rs.u8Rem(self.id, 4), address]; + const removed: rs.u8 | undefined = rs.btreeRemove(_8.value, _12); + rs.refMutRelease(_8); + const same: boolean = rs.addr(self, 12, 4) === address; + if (removed === self.id) { + if (same) { + rs.cellSet(self.left.value, rs.u8WrappingAdd(self.left.value.v, 1)); + } + } +} + +//# sourceMappingURL=pointers.Keyed.ts.map diff --git a/ts/fixtures/lang/pointers/expected/pointers.Small.ts b/ts/fixtures/lang/pointers/expected/pointers.Small.ts new file mode 100644 index 000000000..b14056aa9 --- /dev/null +++ b/ts/fixtures/lang/pointers/expected/pointers.Small.ts @@ -0,0 +1,11 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** A value of no alignment of its own, whose address its `Arc` aligns. */ +export interface Small { + _0: rs.u8; + _1: rs.u8; +} + +//# sourceMappingURL=pointers.Small.ts.map diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 27c5d87df..ef05abfa7 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_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 { 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$tuple2_sync_Weak_pointers_Keyed_usize, 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 { isSomeAnd$1s2u5lz, isSomeAnd$0fp5rqa, 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 type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; import type * as ByCall from "./pointers.ByCall.ts"; @@ -18,6 +18,7 @@ import * as Held from "./pointers.Held.ts"; import type * as Holder from "./pointers.Holder.ts"; import * as Housed from "./pointers.Housed.ts"; import type * as Inner from "./pointers.Inner.ts"; +import * as Keyed from "./pointers.Keyed.ts"; import * as Leaning from "./pointers.Leaning.ts"; import * as Lent from "./pointers.Lent.ts"; import type * as Links from "./pointers.Links.ts"; @@ -35,6 +36,7 @@ import type * as Refilled from "./pointers.Refilled.ts"; import type * as Run from "./pointers.Run.ts"; import * as Segment from "./pointers.Segment.ts"; import * as Shelf from "./pointers.Shelf.ts"; +import type * as Small from "./pointers.Small.ts"; import type * as Span from "./pointers.Span.ts"; import type * as Spot from "./pointers.Spot.ts"; import type * as Stable from "./pointers.Stable.ts"; @@ -2852,6 +2854,241 @@ export function rawPlaces(data: rs.ByteSlice, out: rs.ByteVec): void { num[Symbol.dispose](); } +/** + * Addresses read as integers, as `addr`, and as a raw pointer's `Hash` and `Ord` read + * them: made `Arc`s, dropped ones, and freed ones keyed by address in a B-tree, a hash + * map, and sets of pointers, which must stay unique and aligned among the allocations a + * strong `Arc` or a `Weak` holds, order pointers as their addresses, and stay a `Weak`'s + * once its value drops. Only what Rust fixes is written: equality, counts, alignment, and + * the order's agreement, never an address itself. + */ +export function addresses(data: rs.ByteSlice, out: rs.ByteVec): void { + let _60!: boolean; + let _93!: boolean; + let _110!: boolean; + let _153!: boolean; + let _189!: boolean; + let _241!: boolean; + let agree!: boolean; + let _300!: rs.u8; + const registry: rs.Arc>> = new rs.Arc(new rs.RefCell(rs.btreeNew(rs.cmpTuple<[rs.u8, rs.usize], rs.Ordering>([rs.cmp, rs.cmp])) as rs.BTreeMap<[rs.u8, rs.usize], rs.u8>)); + const left: rs.Arc> = new rs.Arc(new rs.Cell(0)); + const align: rs.usize = 4; + const held: rs.HashMap = rs.hashNew() as rs.HashMap; + const live: rs.Vec> = new rs.Vec(); + const weaks: rs.Vec<[rs.Weak, rs.usize]> = new rs.Vec(); + const ordered: rs.BTreeSet = rs.btreeNew(rs.cmpAddr(12, 4)) as rs.BTreeSet; + const hashed: rs.HashSet = rs.hashNew(rs.hashing(rs.hashAddr(12, 4), rs.sameAddr(12, 4))) as rs.HashSet; + const freed: rs.Vec = new rs.Vec(); + const _17: rs.Weak = new rs.Weak(); + const dangling: Keyed.Keyed = rs.weakAsPtr(_17); + _17[Symbol.dispose](); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(rs.addr(dangling, 12, 4) & 0xff, ((rs.addr(dangling, 12, 4) << 16) >> 16) & 0xff)), undefined, true); + rs.iterExtendBytes(out, rs.iterSlice(rs.bytesMap([(rs.addr(dangling, 12, 4) | 0) === -1, rs.addr(dangling, 12, 4) === 4294967295, rs.addr(rs.NULL, 12, 4) === 0, rs.addr(rs.DANGLING, 12, 4) === align], (a0: boolean): rs.u8 => { + return a0 ? 1 : 0; + })), undefined, true); + const iter: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); + l1: while (true) { + const _41: [rs.usize, rs.u8] | rs.Done = iter.next(); + if (_41 === rs.DONE) { + break; + } + const i: rs.usize = _41[0]; + const byte: rs.u8 = _41[1]; + const _46: rs.u8 = rs.u8Rem(byte, 4); + if (_46 === 0) { + const keyed: rs.Arc = rs.arcAt(new rs.Arc(new Keyed.Keyed(i & 0xff, rs.arcClone(registry), rs.arcClone(left))), 12, 4); + const arc3 = keyed; + const p: Keyed.Keyed = rs.arcRaw(arc3, arc3.value, "Arc"); + const address: rs.usize = rs.addr(p, 12, 4); + b5: { + if (rs.usizeRem(address, align) === 0) { + if (address !== 0) { + _60 = rs.addr(p, 12, 4) === address; + break b5; + } + } + _60 = false; + } + rs.byteVecPush(out, _60 ? 1 : 0); + rs.byteVecPush(out, rs.hashInsert(held, address, keyed.value.id) === undefined ? 1 : 0); + const _78: rs.RefCell> = rs.refCellBorrowMut(registry.value); + const _81: [rs.u8, rs.usize] = [rs.u8Rem(keyed.value.id, 4), rs.addr(p, 12, 4)]; + rs.btreeInsert(_78.value, _81, keyed.value.id); + rs.refMutRelease(_78); + if (!rs.btreeAdd(ordered, p)) { + _93 = false; + } else { + _93 = rs.hashAdd(hashed, p); + } + rs.byteVecPush(out, _93 ? 1 : 0); + for (const f of freed.items) { + const reused: boolean = rs.addr(f, 12, 4) === address; + const _107: Keyed.Keyed[] = [f]; + b10: { + const keys: rs.HashSet = rs.hashExtendKeys(rs.hashNew(rs.hashing(rs.hashAddr(12, 4), rs.sameAddr(12, 4))) as rs.HashSet, rs.iterSlice(_107), undefined, undefined, 234881024); + if (rs.sameAddr(12, 4)(f, p) === reused) { + if (rs.sameAddr(12, 4)(f, p) === reused) { + if (rs.hashContains(keys, p) === reused) { + _110 = rs.cmpAddr(12, 4)(f, p) === 0 === reused; + break b10; + } + } + } + _110 = false; + } + rs.byteVecPush(out, _110 ? 1 : 0); + } + const _125: [rs.Weak, rs.usize] = [new rs.Weak(keyed), address]; + weaks.items.push(_125); + live.items.push(keyed); + if (!((byte & 4) !== 0)) { + continue; + } + const make: (a0: rs.u8) => rs.Arc = (id: rs.u8): rs.Arc => { + return rs.arcAt(new rs.Arc(new Keyed.Keyed(id, rs.arcClone(registry), rs.arcClone(left))), 12, 4); + }; + const pair: rs.Arc[] = [make(i & 0xff), make(i & 0xff)]; + const pointers: Keyed.Keyed[] = rs.arrayMap(rs.arrayFrom(pair), (a02: rs.Arc): Keyed.Keyed => { + const arc4 = a02; + return rs.arcRaw(arc4, arc4.value, "Arc"); + }); + rs.dropAll(pair); + const _150: rs.usize[] = rs.arrayMap(pointers, (p4: Keyed.Keyed): rs.usize => { + return rs.addr(p4, 12, 4); + }); + b12: { + const a: rs.usize = _150[0]; + const b: rs.usize = _150[1]; + if (a !== b) { + if (!rs.hashContains(held, a)) { + _153 = !rs.hashContains(held, b); + break b12; + } + } + _153 = false; + } + rs.byteVecPush(out, _153 ? 1 : 0); + continue; + } + if (_46 === 1) { + if (!(live.items.length === 0)) { + const keyed2: rs.Arc = rs.vecRemove(live, rs.usizeRem(rs.u8Div(byte, 4) as rs.usize, live.items.length)); + const arc2 = keyed2; + const p2: Keyed.Keyed = rs.arcRaw(arc2, arc2.value, "Arc"); + const address2: rs.usize = rs.addr(p2, 12, 4); + rs.byteVecPush(out, rs.hashGet(held, address2) === keyed2.value.id ? 1 : 0); + if (!rs.btreeDelete(ordered, p2)) { + _189 = false; + } else { + _189 = rs.hashDelete(hashed, p2); + } + rs.byteVecPush(out, _189 ? 1 : 0); + keyed2[Symbol.dispose](); + if (!((byte & 4) !== 0)) { + continue; + } + rs.vecRetain(weaks, (_2: [rs.Weak, rs.usize]): boolean => { + return _2[1] !== address2; + }, drop$tuple2_sync_Weak_pointers_Keyed_usize); + rs.hashRemove(held, address2); + freed.items.push(p2); + continue; + } + } else if (_46 === 2) { + const iterS2: [rs.Weak, rs.usize][] = weaks.items; + let iterI2: number = 0; + while (true) { + const _210: [rs.Weak, rs.usize] | rs.Done = iterI2 < iterS2.length ? iterS2[iterI2++] : rs.DONE; + if (_210 === rs.DONE) { + continue l1; + } + const at: rs.usize = rs.addr(rs.weakAsPtr(_210[0]), 12, 4); + const alive: boolean = isSomeAnd$0fp5rqa(rs.weakUpgrade(_210[0]), (keyed3: rs.Arc): boolean => { + const arc = keyed3; + const _02: boolean = rs.addr(rs.arcRaw(arc, arc.value, "Arc"), 12, 4) === at; + keyed3[Symbol.dispose](); + return _02; + }); + rs.byteVecPush(out, rs.u8Add(at === _210[1] ? 1 : 0, rs.u8Mul(2, alive ? 1 : 0))); + rs.byteVecPush(out, rs.hashContains(held, at) ? 1 : 0); + } + } + const addresses: rs.Vec = rs.iterToVec(rs.iterMap(rs.btreeKeys(ordered), (_23: Keyed.Keyed): rs.usize => { + return rs.addr(_23, 12, 4); + }), undefined, 4, true); + if (!rs.sliceIsSorted(rs.sliceFrom(addresses.items), rs.le)) { + _241 = false; + } else { + _241 = addresses.items.length === live.items.length; + } + rs.byteVecPush(out, _241 ? 1 : 0); + const iter4: rs.Iter<[rs.Arc, rs.Arc]> = rs.iterZip(rs.iterSlice(live.items), rs.iterSkip(rs.iterSlice(live.items), 1), [false, false]); + while (true) { + const _259: [rs.Arc, rs.Arc] | rs.Done = iter4.next(); + if (_259 === rs.DONE) { + break; + } + const arc5 = _259[0]; + const _267: Keyed.Keyed = rs.arcRaw(arc5, arc5.value, "Arc"); + const arc6 = _259[1]; + b17: { + const _266: [Keyed.Keyed, Keyed.Keyed] = [_267, rs.arcRaw(arc6, arc6.value, "Arc")]; + const p3: Keyed.Keyed = _267; + const q: Keyed.Keyed = _266[1]; + if (rs.addr(p3, 12, 4) < rs.addr(q, 12, 4) === rs.addr(p3, 12, 4) < rs.addr(q, 12, 4)) { + if (rs.addr(p3, 12, 4) >= rs.addr(q, 12, 4) === rs.addr(p3, 12, 4) >= rs.addr(q, 12, 4)) { + if (rs.cmpAddr(12, 4)(p3, q) === rs.cmp(rs.addr(p3, 12, 4), rs.addr(q, 12, 4))) { + agree = !rs.sameAddr(12, 4)(p3, q); + break b17; + } + } + } + agree = false; + } + rs.byteVecPush(out, agree ? 1 : 0); + } + const registry2: rs.RefCell> = rs.refCellBorrow(registry.value); + let iterStart: rs.u8 = 0; + const iterEnd: rs.u8 = 4; + while ((_300 = iterStart++) < iterEnd) { + const group: rs.u8 = _300; + rs.byteVecPush(out, rs.iterCount(rs.btreeRange(registry2.value, new rs.Range([group, 0] as [rs.u8, rs.usize], [rs.u8Add(group, 1), 0] as [rs.u8, rs.usize]))) & 0xff); + } + rs.byteVecPush(out, rs.iterAll(rs.btreeValues(registry2.value), (id2: rs.u8): boolean => { + return rs.iterAny(rs.hashValues(held), (h: rs.u8): boolean => { + return h === id2; + }); + }) ? 1 : 0); + rs.refRelease(registry2); + } + const small: rs.Arc[] = [rs.arcAt(new rs.Arc({ _0: 1, _1: 2 }), 2, 1), rs.arcAt(new rs.Arc({ _0: 3, _1: 4 }), 2, 1)]; + for (const _332 of small) { + const arc7 = _332; + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(rs.usizeRem(rs.addr(rs.arcRaw(arc7, arc7.value, "Arc"), 2, 1), 4) === 0 ? 1 : 0, rs.addr(rs.DANGLING, 2, 1) === 1 ? 1 : 0, _332.value._0 ^ _332.value._1)), undefined, true); + } + const arc8 = small[0]; + const _359: Small.Small = rs.arcRaw(arc8, arc8.value, "Arc"); + const arc9 = small[1]; + const _363: Small.Small = rs.arcRaw(arc9, arc9.value, "Arc"); + rs.byteVecPush(out, !rs.sameAddr(2, 1)(_359, _363) ? 1 : 0); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(left.value.v, rs.iterCount(rs.iterFilter(rs.iterSlice(weaks.items), (_211: [rs.Weak, rs.usize]): boolean => { + const _56: rs.Arc | undefined = rs.weakUpgrade(_211[0]); + const _010: boolean = _56 !== undefined; + rs.drop(_56); + return _010; + })) & 0xff)), undefined, true); + rs.dropVec(live); + const _382: rs.u8 = left.value.v; + const _389: rs.RefCell> = rs.refCellBorrow(registry.value); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(_382, rs.btreeLen(_389.value) === 0 ? 1 : 0)), undefined, true); + rs.refRelease(_389); + rs.dropAll(small); + rs.dropVec(weaks, drop$tuple2_sync_Weak_pointers_Keyed_usize); + left[Symbol.dispose](); + registry[Symbol.dispose](); +} + /** * Pointers to arrays of other values than bytes, made of the array, of references to it * and its parts, by `first_chunk`, `split_first_chunk`, a subslice pattern, and diff --git a/ts/fixtures/lang/pointers/src/lib.rs b/ts/fixtures/lang/pointers/src/lib.rs index 28baff2b1..c82b8174c 100644 --- a/ts/fixtures/lang/pointers/src/lib.rs +++ b/ts/fixtures/lang/pointers/src/lib.rs @@ -48,6 +48,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("pointers::distinct", |_, data, out| pointers::distinct(data, out)), ("pointers::raw_arcs", |_, data, out| pointers::raw_arcs(data, out)), ("pointers::raw_places", |_, data, out| pointers::raw_places(data, out)), + ("pointers::addresses", |_, data, out| pointers::addresses(data, out)), ("pointers::arrays", |_, data, out| pointers::arrays(data, out)), ] } diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index 10bfc8ffd..a72eb0419 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -11,6 +11,7 @@ //! raw pointers of an `Arc` and an `Rc`, counted and taken back. use std::cell::{Cell, UnsafeCell}; +use std::collections::BTreeMap; use std::mem::{self, MaybeUninit}; use std::ptr::{self, NonNull}; use std::rc::Rc; @@ -2273,6 +2274,166 @@ pub fn raw_places(data: &[u8], out: &mut Vec) { /// What holds an `Arc` a reference to it borrows through. struct Shelf(Arc); +/// A value keyed by its address in a registry it leaves as it drops, as a cache keys +/// what it tracks: by `Arc::as_ptr` as it is made, and by `ptr::from_ref` and a cast of +/// `self` as it drops, which must be the same address. +struct Keyed { + id: u8, + registry: Rc>>, + left: Rc>, +} + +impl Drop for Keyed { + fn drop(&mut self) { + let address = ptr::from_ref::(self) as usize; + let removed = self.registry.borrow_mut().remove(&(self.id % 4, address)); + let same = self as *const Self as usize == address; + if removed == Some(self.id) && same { + self.left.set(self.left.get().wrapping_add(1)); + } + } +} + +/// Addresses read as integers, as `addr`, and as a raw pointer's `Hash` and `Ord` read +/// them: made `Arc`s, dropped ones, and freed ones keyed by address in a B-tree, a hash +/// map, and sets of pointers, which must stay unique and aligned among the allocations a +/// strong `Arc` or a `Weak` holds, order pointers as their addresses, and stay a `Weak`'s +/// once its value drops. Only what Rust fixes is written: equality, counts, alignment, and +/// the order's agreement, never an address itself. +pub fn addresses(data: &[u8], out: &mut Vec) { + use std::collections::{BTreeSet, HashMap, HashSet}; + use std::sync::Weak; + let registry = Rc::new(std::cell::RefCell::new(BTreeMap::new())); + let left = Rc::new(Cell::new(0u8)); + let align = align_of::(); + let mut held: HashMap = HashMap::new(); + let mut live: Vec> = Vec::new(); + let mut weaks: Vec<(Weak, usize)> = Vec::new(); + let mut ordered: BTreeSet<*const Keyed> = BTreeSet::new(); + let mut hashed: HashSet<*const Keyed> = HashSet::new(); + let mut freed: Vec<*const Keyed> = Vec::new(); + let dangling = Weak::::new().as_ptr(); + out.extend([dangling as u8, dangling as i16 as u8]); + out.extend( + [ + dangling as isize == -1, + dangling as usize == usize::MAX, + ptr::null::() as usize == 0, + NonNull::::dangling().as_ptr().addr() == align, + ] + .map(u8::from), + ); + for (i, &byte) in data.iter().enumerate() { + match byte % 4 { + 0 => { + let keyed = Arc::new(Keyed { + id: i as u8, + registry: Rc::clone(®istry), + left: Rc::clone(&left), + }); + let p = Arc::as_ptr(&keyed); + let address = p as usize; + out.push(u8::from(address % align == 0 && address != 0 && p.addr() == address)); + // Apart from every allocation still held, a freed one's included. + out.push(u8::from(held.insert(address, keyed.id).is_none())); + registry + .borrow_mut() + .insert((keyed.id % 4, p.expose_provenance()), keyed.id); + out.push(u8::from(ordered.insert(p) && hashed.insert(p))); + // A pointer to a freed allocation equals one to an allocation reusing its + // address, and hashes and orders as it. + for &f in &freed { + let reused = f as usize == address; + let keys = HashSet::from([f]); + out.push(u8::from( + (f == p) == reused + && ptr::eq(f, p) == reused + && keys.contains(&p) == reused + && f.cmp(&p).is_eq() == reused, + )); + } + weaks.push((Arc::downgrade(&keyed), address)); + live.push(keyed); + // Two made and freed together, read only after: apart from each other and + // from every allocation held while they lived. + if byte & 4 != 0 { + let make = |id: u8| { + Arc::new(Keyed { + id, + registry: Rc::clone(®istry), + left: Rc::clone(&left), + }) + }; + let pair = [make(i as u8), make(i as u8)]; + let pointers = pair.each_ref().map(Arc::as_ptr); + drop(pair); + let [a, b] = pointers.map(|p| p as usize); + out.push(u8::from(a != b && !held.contains_key(&a) && !held.contains_key(&b))); + } + } + 1 if !live.is_empty() => { + let keyed = live.remove(usize::from(byte / 4) % live.len()); + let p = Arc::as_ptr(&keyed); + let address = p as usize; + out.push(u8::from(held.get(&address) == Some(&keyed.id))); + out.push(u8::from(ordered.remove(&p) && hashed.remove(&p))); + drop(keyed); + // Its `Weak` too, which frees the allocation for another to reuse. + if byte & 4 != 0 { + weaks.retain(|(_, at)| *at != address); + held.remove(&address); + freed.push(p); + } + } + // A `Weak` keeps its address once its value drops, as it keeps the allocation. + 2 => { + for (weak, address) in &weaks { + let at = weak.as_ptr() as usize; + let alive = weak.upgrade().is_some_and(|keyed| Arc::as_ptr(&keyed) as usize == at); + out.push(u8::from(at == *address) + 2 * u8::from(alive)); + out.push(u8::from(held.contains_key(&at))); + } + } + _ => { + let addresses: Vec = ordered.iter().map(|&p| p as usize).collect(); + out.push(u8::from(addresses.is_sorted() && addresses.len() == live.len())); + for (a, b) in live.iter().zip(live.iter().skip(1)) { + let (p, q) = (Arc::as_ptr(a), Arc::as_ptr(b)); + let agree = (p < q) == ((p as usize) < (q as usize)) + && (p >= q) == ((p as usize) >= (q as usize)) + && p.cmp(&q) == (p as usize).cmp(&(q as usize)) + && p != q; + out.push(u8::from(agree)); + } + let registry = registry.borrow(); + for group in 0..4u8 { + out.push(registry.range((group, 0)..(group + 1, 0)).count() as u8); + } + out.push(u8::from(registry.values().all(|id| held.values().any(|h| h == id)))); + } + } + } + // A value of no alignment of its own, aligned as the counts before it in its `Arc`. + let small = [Arc::new(Small(1, 2)), Arc::new(Small(3, 4))]; + for arc in &small { + out.extend([ + u8::from(Arc::as_ptr(arc).addr() % align_of::() == 0), + u8::from(NonNull::::dangling().as_ptr().addr() == align_of::()), + arc.0 ^ arc.1, + ]); + } + out.push(u8::from(Arc::as_ptr(&small[0]) != Arc::as_ptr(&small[1]))); + out.extend([ + left.get(), + weaks.iter().filter(|(weak, _)| weak.upgrade().is_some()).count() as u8, + ]); + drop(live); + out.extend([left.get(), u8::from(registry.borrow().is_empty())]); +} + +/// A value of no alignment of its own, whose address its `Arc` aligns. +struct Small(u8, u8); + /// Pointers to arrays of other values than bytes, made of the array, of references to it /// and its parts, by `first_chunk`, `split_first_chunk`, a subslice pattern, and /// `array::from_mut`: compared where they point, and written through. diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index d7acc7b88..ab606d0de 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -1,4 +1,32 @@ -fixtures/reject/src/alloc.rs:addressed+1:17: a pointer's address, which JS has no counterpart of +fixtures/reject/src/addresses.rs:borrowed+2:2: the address of a pointer to `addresses::Borrowed`, which a local keeps that the program borrows, drops, or gives std, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:boxed+2:2: the address of a pointer to `addresses::Boxed`, which `std::boxed::Box` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:cyclic+3:10: `Weak::as_ptr` of a `Weak`, which `Arc::new_cyclic` makes and JS has no value of until its closure returns +fixtures/reject/src/addresses.rs:dropped+3:2: the address of a pointer to `addresses::Dropped`, which a local keeps that the program borrows, drops, or gives std, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:elements+1:19: the address of a pointer to `addresses::Element`, which `[addresses::Element]` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:held+1:2: the address of a pointer to `addresses::Held`, which a static or constant holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:inner+1:11: the address of a pointer to `addresses::Inner`, which `std::option::Option` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:inner+2:7: `Arc::into_inner` of an `Arc` or `Rc` of `addresses::Inner`, which moves it out of the allocation whose address the program reads +fixtures/reject/src/addresses.rs:made+1:5: a pointer made of an address, which JS has no memory at +fixtures/reject/src/addresses.rs:made+1:20: a pointer made of an address, which JS has no memory at +fixtures/reject/src/addresses.rs:made+1:54: a cast from `usize` to `*const addresses::Element`, between a pointer and an integer +fixtures/reject/src/addresses.rs:parts+1:34: the address of a pointer to `addresses::Part`, which `addresses::Pair` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:parts+1:76: the address of a pointer to `addresses::Part`, which `addresses::Pair` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:place_keys+1:13: the address of a pointer to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:place_keys+1:47: a raw pointer to `u32` behind a reference, which is the object and not where it is kept +fixtures/reject/src/addresses.rs:place_keys+2:15: comparing pointers to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:place_keys+2:15: the address of a pointer to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:place_keys+2:48: a raw pointer to `u32` behind a reference, which is the object and not where it is kept +fixtures/reject/src/addresses.rs:place_keys+3:32: a raw pointer to `u32` behind a reference, which is the object and not where it is kept +fixtures/reject/src/addresses.rs:place_keys+3:32: the address of a pointer to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:place_keys+3:50: a raw pointer to `u32` behind a reference, which is the object and not where it is kept +fixtures/reject/src/addresses.rs:places+1:2: the address of a pointer to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:places+1:35: a raw pointer to a place of `()`, which has no address of its own +fixtures/reject/src/addresses.rs:places+1:35: the address of a pointer to `()`, which has no bytes and so no address of its own +fixtures/reject/src/addresses.rs:rewritten+4:2: the address of a pointer to `addresses::Rewritten`, which the crate writes whole in place, moving another into its `Arc`, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:unshared+1:2: `Arc::make_mut` of an `Arc` or `Rc` of `addresses::Unshared`, which moves it out of the allocation whose address the program reads +fixtures/reject/src/addresses.rs:unwrapped+1:2: unsupported call to `std::sync::Arc::::try_unwrap` +fixtures/reject/src/addresses.rs:unwrapped+1:55: unsupported call to `std::sync::Arc::::unwrap_or_clone` +fixtures/reject/src/alloc.rs:addressed+1:17: the address of a pointer to `u32`, each a place JS makes of its own fixtures/reject/src/alloc.rs:alloc_retyped+4:7: memory from `alloc` cast to pointers to `u32` and to `i32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:alloc_retyped+5:11: 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:alloc_retyped+7:17: memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another @@ -174,10 +202,10 @@ fixtures/reject/src/pointers.rs:assigned_through+2:10: a raw pointer to `(u32, u fixtures/reject/src/pointers.rs:byte_option_slice+1:4: a slice of one byte's place, which a byte slice can view only in an array of bytes fixtures/reject/src/pointers.rs:byte_pointer_ops+5:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in fixtures/reject/src/pointers.rs:byte_pointer_ops+6:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in -fixtures/reject/src/pointers.rs:byte_pointer_ops+7:14: comparing `*mut [u8; 4]` +fixtures/reject/src/pointers.rs:byte_pointer_ops+7:14: the address of a pointer to `[u8; 4]`, a view JS makes of its own of an array that may be in a larger one fixtures/reject/src/pointers.rs:byte_pointer_ops+8:56: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in fixtures/reject/src/pointers.rs:byte_pointer_ops+8:73: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in -fixtures/reject/src/pointers.rs:byte_pointer_ops+11:5: hashing `*const [u8; 4]` by its address, which JS has no counterpart of +fixtures/reject/src/pointers.rs:byte_pointer_ops+11:5: the address of a pointer to `[u8; 4]`, a view JS makes of its own of an array that may be in a larger one fixtures/reject/src/pointers.rs:byte_pointer_parts+3:15: a raw pointer to `[u8; 3]` in an element, a payload, or a union field, which is not supported yet fixtures/reject/src/pointers.rs:byte_pointer_parts+3:18: a raw pointer to `[u8; 3]` in an element, a payload, or a union field, which is not supported yet fixtures/reject/src/pointers.rs:byte_pointer_parts+3:37: a raw pointer to `[u8; 2]` in an element, a payload, or a union field, which is not supported yet @@ -231,7 +259,7 @@ fixtures/reject/src/pointers.rs:pointers_apart+11:24: `offset_from` between a po fixtures/reject/src/pointers.rs:pointers_apart+12:23: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_apart+14:21: `offset_from` between a pointer into a shared slice JS copies and one into a value in no array, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_ordered+2:2: comparing pointers to `u32`, each a place JS makes of its own -fixtures/reject/src/pointers.rs:pointers_ordered+2:2: ordering `*const u32` by its address, which JS has no counterpart of, in `std::cmp::max` +fixtures/reject/src/pointers.rs:pointers_ordered+2:2: the address of a pointer to `u32`, each a place JS makes of its own, in `std::cmp::max` fixtures/reject/src/pointers.rs:put_replaced+2:10: a raw pointer to `(u32, u32)` in `pointers::Plain`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:range_of+1:2: a raw pointer to `(u32, u32)` in `pointers::Window`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:rc_raw_bytes+1:11: an `Rc`'s raw pointer to `[u8; 4]`, which JS makes anew each time diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index fb9f70ed7..e45a3966b 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -2,7 +2,36 @@ no item `bound_ranges` in the crate no item `clones::slice_of_object` in the crate no item `lopsided` in the crate no item `missing` in the crate -fixtures/reject/src/alloc.rs:addressed+1:17: a pointer's address, which JS has no counterpart of +fixtures/reject/src/addresses.rs:borrowed+2:2: the address of a pointer to `addresses::Borrowed`, which a local keeps that the program borrows, drops, or gives std, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:boxed+2:2: the address of a pointer to `addresses::Boxed`, which `std::boxed::Box` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:cyclic+3:10: `Weak::as_ptr` of a `Weak`, which `Arc::new_cyclic` makes and JS has no value of until its closure returns +fixtures/reject/src/addresses.rs:dropped+3:2: the address of a pointer to `addresses::Dropped`, which a local keeps that the program borrows, drops, or gives std, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:elements+1:19: the address of a pointer to `addresses::Element`, which `[addresses::Element]` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:given_at+1:2: the address of a pointer to `addresses::Given`, which JS keeps, as `addresses::given` takes or returns one, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:held+1:2: the address of a pointer to `addresses::Held`, which a static or constant holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:inner+1:11: the address of a pointer to `addresses::Inner`, which `std::option::Option` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:inner+2:7: `Arc::into_inner` of an `Arc` or `Rc` of `addresses::Inner`, which moves it out of the allocation whose address the program reads +fixtures/reject/src/addresses.rs:made+1:5: a pointer made of an address, which JS has no memory at +fixtures/reject/src/addresses.rs:made+1:20: a pointer made of an address, which JS has no memory at +fixtures/reject/src/addresses.rs:made+1:54: a cast from `usize` to `*const addresses::Element`, between a pointer and an integer +fixtures/reject/src/addresses.rs:parts+1:34: the address of a pointer to `addresses::Part`, which `addresses::Pair` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:parts+1:76: the address of a pointer to `addresses::Part`, which `addresses::Pair` holds, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:place_keys+1:13: the address of a pointer to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:place_keys+1:47: a raw pointer to `u32` behind a reference, which is the object and not where it is kept +fixtures/reject/src/addresses.rs:place_keys+2:15: comparing pointers to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:place_keys+2:15: the address of a pointer to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:place_keys+2:48: a raw pointer to `u32` behind a reference, which is the object and not where it is kept +fixtures/reject/src/addresses.rs:place_keys+3:32: a raw pointer to `u32` behind a reference, which is the object and not where it is kept +fixtures/reject/src/addresses.rs:place_keys+3:32: the address of a pointer to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:place_keys+3:50: a raw pointer to `u32` behind a reference, which is the object and not where it is kept +fixtures/reject/src/addresses.rs:places+1:2: the address of a pointer to `u32`, each a place JS makes of its own +fixtures/reject/src/addresses.rs:places+1:35: a raw pointer to a place of `()`, which has no address of its own +fixtures/reject/src/addresses.rs:places+1:35: the address of a pointer to `()`, which has no bytes and so no address of its own +fixtures/reject/src/addresses.rs:rewritten+4:2: the address of a pointer to `addresses::Rewritten`, which the crate writes whole in place, moving another into its `Arc`, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does +fixtures/reject/src/addresses.rs:unshared+1:2: `Arc::make_mut` of an `Arc` or `Rc` of `addresses::Unshared`, which moves it out of the allocation whose address the program reads +fixtures/reject/src/addresses.rs:unwrapped+1:2: unsupported call to `std::sync::Arc::::try_unwrap` +fixtures/reject/src/addresses.rs:unwrapped+1:55: unsupported call to `std::sync::Arc::::unwrap_or_clone` +fixtures/reject/src/alloc.rs:addressed+1:17: the address of a pointer to `u32`, each a place JS makes of its own fixtures/reject/src/alloc.rs:alloc_retyped+4:7: memory from `alloc` cast to pointers to `u32` and to `i32`, which reads the bytes of one type as another fixtures/reject/src/alloc.rs:alloc_retyped+5:11: 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:alloc_retyped+7:17: memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another @@ -176,10 +205,10 @@ fixtures/reject/src/pointers.rs:assigned_through+2:10: a raw pointer to `(u32, u fixtures/reject/src/pointers.rs:byte_option_slice+1:4: a slice of one byte's place, which a byte slice can view only in an array of bytes fixtures/reject/src/pointers.rs:byte_pointer_ops+5:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in fixtures/reject/src/pointers.rs:byte_pointer_ops+6:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in -fixtures/reject/src/pointers.rs:byte_pointer_ops+7:14: comparing `*mut [u8; 4]` +fixtures/reject/src/pointers.rs:byte_pointer_ops+7:14: the address of a pointer to `[u8; 4]`, a view JS makes of its own of an array that may be in a larger one fixtures/reject/src/pointers.rs:byte_pointer_ops+8:56: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in fixtures/reject/src/pointers.rs:byte_pointer_ops+8:73: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in -fixtures/reject/src/pointers.rs:byte_pointer_ops+11:5: hashing `*const [u8; 4]` by its address, which JS has no counterpart of +fixtures/reject/src/pointers.rs:byte_pointer_ops+11:5: the address of a pointer to `[u8; 4]`, a view JS makes of its own of an array that may be in a larger one fixtures/reject/src/pointers.rs:byte_pointer_parts+3:15: a raw pointer to `[u8; 3]` in an element, a payload, or a union field, which is not supported yet fixtures/reject/src/pointers.rs:byte_pointer_parts+3:18: a raw pointer to `[u8; 3]` in an element, a payload, or a union field, which is not supported yet fixtures/reject/src/pointers.rs:byte_pointer_parts+3:37: a raw pointer to `[u8; 2]` in an element, a payload, or a union field, which is not supported yet @@ -233,7 +262,7 @@ fixtures/reject/src/pointers.rs:pointers_apart+11:24: `offset_from` between a po fixtures/reject/src/pointers.rs:pointers_apart+12:23: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_apart+14:21: `offset_from` between a pointer into a shared slice JS copies and one into a value in no array, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_ordered+2:2: comparing pointers to `u32`, each a place JS makes of its own -fixtures/reject/src/pointers.rs:pointers_ordered+2:2: ordering `*const u32` by its address, which JS has no counterpart of, in `std::cmp::max` +fixtures/reject/src/pointers.rs:pointers_ordered+2:2: the address of a pointer to `u32`, each a place JS makes of its own, in `std::cmp::max` fixtures/reject/src/pointers.rs:put_replaced+2:10: a raw pointer to `(u32, u32)` in `pointers::Plain`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:range_of+1:2: a raw pointer to `(u32, u32)` in `pointers::Window`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:rc_raw_bytes+1:11: an `Rc`'s raw pointer to `[u8; 4]`, which JS makes anew each time diff --git a/ts/fixtures/reject/src/addresses.rs b/ts/fixtures/reject/src/addresses.rs new file mode 100644 index 000000000..59580b324 --- /dev/null +++ b/ts/fixtures/reject/src/addresses.rs @@ -0,0 +1,152 @@ +//! Addresses read as integers where JS has no exact counterpart: of values the program +//! keeps anywhere but as an `Arc`'s value (a local it borrows or drops, a field, a `Box`, +//! a collection, a static, or where JS keeps it) or moves out of its `Arc`, of places, of +//! values of no bytes, of a `Weak` whose value `Arc::new_cyclic` makes, and pointers made +//! of addresses. + +use std::sync::{Arc, Weak}; + +/// What a `Vec` holds many of. +pub struct Element(pub u32); + +/// The addresses of a `Vec`'s elements, which Rust keeps a fixed distance apart. +pub fn elements(v: &Vec) -> Option { + v.iter().map(|e| std::ptr::from_ref(e) as usize).max() +} + +/// One of two parts of a `Pair`. +pub struct Part(pub u32); + +/// What holds two `Part`s. +pub struct Pair { + pub first: Part, + pub second: Part, +} + +/// The addresses of a struct's two parts, which Rust orders as its layout does. +pub fn parts(pair: &Pair) -> bool { + std::ptr::from_ref(&pair.first).addr() < std::ptr::from_ref(&pair.second).addr() +} + +/// The address of a place, which JS makes anew each borrow, and of a value of no bytes. +pub fn places(n: &u32, unit: &()) -> usize { + std::ptr::from_ref(n) as usize ^ std::ptr::from_ref(unit) as usize +} + +/// Raw pointers to places ordered and hashed by their addresses. +pub fn place_keys(a: &u32, b: &u32) -> bool { + let keys = std::collections::BTreeSet::from([&raw const *a]); + let hashed = std::collections::HashSet::from([&raw const *b]); + keys.len() == hashed.len() && (&raw const *a) < (&raw const *b) +} + +/// What `Arc::new_cyclic` makes, whose `Weak` points at no value until it returns. +pub struct Cyclic { + pub me: Weak, + pub at: usize, +} + +/// `Weak::as_ptr` before the value is made. +pub fn cyclic() -> Arc { + Arc::new_cyclic(|me| Cyclic { + me: me.clone(), + at: me.as_ptr() as usize, + }) +} + +/// Pointers made of addresses, which JS has no memory at. +pub fn made(p: *const Element, at: usize) -> [*const Element; 3] { + [p.with_addr(at), std::ptr::without_provenance(at), at as *const Element] +} + +/// What a local holds, borrowed. +pub struct Borrowed(pub u8); + +/// The address of a local, which JS has no allocation of to free. +pub fn borrowed() -> usize { + let local = Borrowed(1); + std::ptr::from_ref(&local) as usize +} + +/// What a local holds until it drops, and an `Arc` holds elsewhere. +pub struct Dropped(pub u8); + +impl Drop for Dropped { + fn drop(&mut self) { + self.0 = 0; + } +} + +/// The address of an `Arc`'s `Dropped`, of a type a local drops too. +pub fn dropped() -> usize { + let shared = Arc::new(Dropped(1)); + let _local = Dropped(2); + Arc::as_ptr(&shared) as usize +} + +/// What a `Box` holds. +pub struct Boxed(pub u8); + +/// The address of a box's value. +pub fn boxed() -> usize { + let b = Box::new(Boxed(1)); + std::ptr::from_ref(&*b) as usize +} + +/// What a static holds. +pub struct Held(pub u8); + +static HELD: Held = Held(1); + +/// The address of a static. +pub fn held() -> usize { + std::ptr::from_ref(&HELD) as usize +} + +/// What JS is given whole. +pub struct Given(pub u8); + +/// A `Given` JS keeps where it likes. +pub fn given() -> Given { + Given(1) +} + +/// The address of a `Given`, which JS may keep anywhere. +pub fn given_at(given: &Given) -> usize { + std::ptr::from_ref(given) as usize +} + +/// What `Arc::into_inner` moves out. +pub struct Inner(pub u8); + +/// The address of an `Arc`'s value, and the value moved out of it. +pub fn inner(shared: Arc) -> usize { + let at = Arc::as_ptr(&shared) as usize; + at ^ Arc::into_inner(shared).map_or(0, |inner| usize::from(inner.0)) +} + +/// The values `Arc::try_unwrap` and `unwrap_or_clone` move out, which translate for no type. +pub fn unwrapped(shared: Arc, other: Arc) -> u8 { + Arc::try_unwrap(shared).map_or(0, |inner| inner.0) ^ Arc::unwrap_or_clone(other).0 +} + +/// What `Arc::make_mut` may move to a new `Arc`. +#[derive(Clone)] +pub struct Unshared(pub u8); + +/// The address of an `Arc`'s value made its own. +pub fn unshared(shared: &mut Arc) -> usize { + Arc::make_mut(shared).0 += 1; + Arc::as_ptr(shared) as usize +} + +/// What `Arc::get_mut` writes whole. +pub struct Rewritten(pub u8); + +/// The address of an `Arc`'s value written whole through `get_mut`. +pub fn rewritten(shared: &mut Arc) -> usize { + if let Some(value) = Arc::get_mut(shared) { + *value = Rewritten(2); + } + Arc::as_ptr(shared) as usize +} diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index 83d03c654..31f86f10c 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -4,6 +4,7 @@ #![allow(dead_code, reason = "each item is a translation root, not called from Rust")] #![feature(cow_is_borrowed, extend_one, f16, is_ascii_octdigit, linked_list_retain)] +mod addresses; mod alloc; mod buffers; mod clones; diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index b5754cba1..063c166c7 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -1,5 +1,21 @@ package = "reject-fixture" roots = [ + "addresses::borrowed", + "addresses::boxed", + "addresses::cyclic", + "addresses::dropped", + "addresses::elements", + "addresses::given", + "addresses::given_at", + "addresses::held", + "addresses::inner", + "addresses::made", + "addresses::parts", + "addresses::place_keys", + "addresses::places", + "addresses::rewritten", + "addresses::unshared", + "addresses::unwrapped", "alloc::addressed", "alloc::alloc_retyped", "alloc::allocated", diff --git a/ts/runtime/rs.ts b/ts/runtime/rs.ts index 1a726feee..7ddf376f3 100644 --- a/ts/runtime/rs.ts +++ b/ts/runtime/rs.ts @@ -299,7 +299,7 @@ const UNHELD = /* @__PURE__ */ new FinalizationRegistry((addr) => { }); /** The pointer at `addr`, which no memory is at: the same `Address` as every other one there. */ -function addressAt(addr: number): Address { +export function addressAt(addr: number): Address { let at = ADDRESSES.get(addr)?.deref(); if (at === undefined) { at = new Address(addr); @@ -313,6 +313,74 @@ function nowhere(): never { throw new Error("rust2-ts: a read or write through a pointer to no memory"); } +/** The address of each `Arc`'s or `Rc`'s value that `addrNew` gave one. */ +const ADDRESSED = /* @__PURE__ */ new WeakMap(); + +/** The block each address `addrNew` gave is in, by its size and alignment, and where it starts, while its allocation lives. */ +const BLOCKS = /* @__PURE__ */ new Map(); + +/** The starts of freed blocks, by their size and alignment. */ +const FREED = /* @__PURE__ */ new Map(); + +/** Where the next new block starts: past wasm32's first page, as a heap is. */ +let brk = 0x10000; + +/** Whether `addrNew` has given an address, after which freeing an allocation frees its own. */ +let addressing = false; + +/** + * `p as usize`, of a pointer to a value of `size` bytes aligned to `align`: null's 0, a + * dangling pointer's `align`, an address no memory is at, or else that of the `Arc`'s or + * `Rc`'s value it points at, which the allocation keeps once freed, as a pointer to it + * does in Rust. + */ +export function addr(p: object, size: number, align: number): number { + if (p === NULL) return 0; + if (p === DANGLING) return align; + if (p instanceof Address) return p.addr; + return ADDRESSED.get(p) ?? addrNew(p, size, align); +} + +/** `==` of raw pointers to values of `size` bytes aligned to `align`, which compares their addresses. */ +export function sameAddr(size: number, align: number): (a: object, b: object) => boolean { + return (a, b) => addr(a, size, align) === addr(b, size, align); +} + +/** + * Give `value`, a new `Arc`'s or `Rc`'s of `size` bytes aligned to `align`, its address, + * as an allocator places the allocation: after its two counts, at a multiple of `align`, + * in a block no live allocation's overlaps, reusing a freed one, which `unaddress` frees. + * Running out of wasm32's addresses aborts, as a failed allocation does. + */ +export function addrNew(value: object, size: number, align: number): number { + addressing = true; + const head = Math.ceil(8 / align) * align; + const whole = Math.max(align, 4); + const block = `${head + size}:${whole}`; + let start = FREED.get(block)?.pop(); + if (start === undefined) { + start = Math.ceil(brk / whole) * whole; + if (start + head + size > 2 ** 32) throw new Abort(`memory allocation of ${size} bytes failed`); + brk = start + head + size; + } + ADDRESSED.set(value, start + head); + BLOCKS.set(start + head, [block, start]); + return start + head; +} + +/** Free the address of `value`, whose `Arc` or `Rc` allocation Rust frees now. */ +export function unaddress(value: unknown): void { + if (!addressing || (typeof value !== "object" && typeof value !== "function") || value === null) return; + const at = ADDRESSED.get(value); + const held = at === undefined ? undefined : BLOCKS.get(at); + if (held === undefined) return; + BLOCKS.delete(at as number); + const [block, start] = held; + let free = FREED.get(block); + if (free === undefined) FREED.set(block, (free = [])); + free.push(start); +} + /** The array of one element that a pointer to a value in no array points into, by its place. */ const LONE = /* @__PURE__ */ new WeakMap, unknown[]>(); diff --git a/ts/runtime/rs/collections.ts b/ts/runtime/rs/collections.ts index 72dc7a5ab..6d94a15e5 100644 --- a/ts/runtime/rs/collections.ts +++ b/ts/runtime/rs/collections.ts @@ -15,7 +15,7 @@ // A function that may drop a key, value, or item std's would, rather than return or keep // it, takes what drops each that does, as `k`, `v`, or `f`, and drops it where std's does. -import { ByteSlice, ByteVec, byteVecPush, byteVecReserve, capReserve, capReserveExact, capTryReserve, capTryReserveExact, type CloneFrom, type Debug, debugStructField, debugStructFinishNonExhaustive, Err, type Fmt, fmtDebugStruct, fmtDebugStructFields, fmtDebugTupleFields, fmtIter, fmtList, type FmtResult, fmtSet, fmtTuple, type Formatter, type None, OK, Ok, panic, type Place, type RangeBound, rangeCheck, type Result, searchBy, type Elements, Slice, sliceData, sliceFrom, sliceLength, sliceSort, sliceStart, Vec, vecAppend, vecCapacity, vecCloneFrom, vecFrom, vecHold, vecKeep, vecMax, vecReserve, vecWithCapacity } from "../rs.ts"; +import { addr, ByteSlice, ByteVec, byteVecPush, byteVecReserve, capReserve, capReserveExact, capTryReserve, capTryReserveExact, type CloneFrom, type Debug, debugStructField, debugStructFinishNonExhaustive, Err, type Fmt, fmtDebugStruct, fmtDebugStructFields, fmtDebugTupleFields, fmtIter, fmtList, type FmtResult, fmtSet, fmtTuple, type Formatter, type None, OK, Ok, panic, type Place, type RangeBound, rangeCheck, type Result, searchBy, type Elements, Slice, sliceData, sliceFrom, sliceLength, sliceSort, sliceStart, Vec, vecAppend, vecCapacity, vecCloneFrom, vecFrom, vecHold, vecKeep, vecMax, vecReserve, vecWithCapacity } from "../rs.ts"; import { allocError, CAPACITY_OVERFLOW, type TryReserveError } from "./alloc.ts"; import type { Hashing } from "./hash.ts"; import { type Limbs, lt, type u64, type usize } from "./int.ts"; @@ -64,6 +64,11 @@ export function cmp(a: T, b: T): Ordering { return lt(x, y) ? -1 : lt(y, x) ? 1 : 0; } +/** `Ord::cmp` of raw pointers to values of `size` bytes aligned to `align`, which compares their addresses. */ +export function cmpAddr(size: number, align: number): (a: object, b: object) => Ordering { + return (a, b) => cmp(addr(a, size, align), addr(b, size, align)); +} + /** * `Ord::cmp` of two strings, by code point as Rust's UTF-8 bytes sort. JS's `<` compares * UTF-16 units, which puts a character past U+FFFF, a surrogate pair, below U+E000. diff --git a/ts/runtime/rs/hash.ts b/ts/runtime/rs/hash.ts index 5762270f8..b80ca8fc1 100644 --- a/ts/runtime/rs/hash.ts +++ b/ts/runtime/rs/hash.ts @@ -5,7 +5,7 @@ // A hash collection hashes a key that is a JS object with a faster hasher of its own, // whose values nothing sees, as std keys a map's hasher at random. -import { ByteSlice, type ByteVec, type Elements, type None, Slice, sliceData, sliceItems, sliceLength, sliceStart, Vec } from "../rs.ts"; +import { addr, ByteSlice, type ByteVec, type Elements, type None, Slice, sliceData, sliceItems, sliceLength, sliceStart, Vec } from "../rs.ts"; import { type i8, type i16, @@ -639,6 +639,11 @@ export function hashUsize(value: number, s: Hasher): void { s.usize(value); } +/** `Hash::hash` of a raw pointer to a value of `size` bytes aligned to `align`, which hashes its address. */ +export function hashAddr(size: number, align: number): (p: object, s: Hasher) => void { + return (p, s) => s.usize(addr(p, size, align)); +} + /** `Hash::hash` of an `i8`, or of a discriminant of `Ordering`, whose `repr` it is. */ export function hashI8(value: number, s: Hasher): void { s.i8(value); diff --git a/ts/runtime/rs/task.ts b/ts/runtime/rs/task.ts index 26db5ac64..4657afb9e 100644 --- a/ts/runtime/rs/task.ts +++ b/ts/runtime/rs/task.ts @@ -4,7 +4,7 @@ // A value whose drop JS can observe has `[Symbol.dispose]`, which runs its `Drop` impl // and drops its fields. `None` is `undefined`, which drops as nothing. -import { Abort, arrayAssign, type ByteSlice, type CloneFrom, cloneOver, type Debug, type DebugStruct, debugStructField, debugStructFinish, debugStructFinishNonExhaustive, debugTupleField, debugTupleFinish, Err, type FmtResult, type Formatter, fmtBool, fmtDebugStruct, fmtDebugTuple, fmtDebugTupleFields, fmtPad, fmtStr, type None, NULL, objectCopy, OK, Ok, Panic, panic, type Place, place, ptrErase, ptrHome, ptrNowhere, ptrWrite, type Result, Slice, stringAsBytes, type Vec } from "../rs.ts"; +import { Abort, addressAt, addrNew, arrayAssign, type ByteSlice, type CloneFrom, cloneOver, type Debug, type DebugStruct, debugStructField, debugStructFinish, debugStructFinishNonExhaustive, debugTupleField, debugTupleFinish, Err, type FmtResult, type Formatter, fmtBool, fmtDebugStruct, fmtDebugTuple, fmtDebugTupleFields, fmtPad, fmtStr, type None, NULL, objectCopy, OK, Ok, Panic, panic, type Place, place, ptrErase, ptrHome, ptrNowhere, ptrWrite, type Result, Slice, stringAsBytes, unaddress, type Vec } from "../rs.ts"; import type { Hasher } from "./hash.ts"; import type { Location } from "./location.ts"; @@ -125,10 +125,29 @@ export class Arc implements Disposable { } [Symbol.dispose](): void { - if (--this.strong === 0) drop(this.value); + if (--this.strong === 0) { + this.weak++; + drop(this.value); + release(this); + } } } +/** `arc`, a new `Arc` or `Rc` of a value of `size` bytes aligned to `align`, given its address as it is made. */ +export function arcAt(arc: Arc, size: number, align: number): Arc { + addrNew(arc.value as object, size, align); + return arc; +} + +/** + * Release the weak count `arc` holds while its last strong one drops its value, as std's + * strong `Arc`s share one, freeing the allocation if no `Weak` is left. + */ +function release(arc: Arc): void { + const root = arcRoot(arc); + if (--root.weak === 0) unaddress(root.value); +} + /** * `Arc` unsized from `arc`, which it shares one count with, as Rust's one * allocation does: `value` is the trait object over `arc`'s value, which the last of @@ -160,7 +179,11 @@ export class ArcDyn implements Disposable { } [Symbol.dispose](): void { - if (--this.arc.strong === 0) drop(this.value); + if (--this.arc.strong === 0) { + this.arc.weak++; + drop(this.value); + release(this.arc); + } } } @@ -218,7 +241,11 @@ export function arcStrBytes(arc: Arc): Arc { * by `f`, or not at all without one, as a borrow. */ export function arcDrop(arc: Arc, f?: (value: T) => void): void { - if (--arc.strong === 0) f?.(arc.value); + if (--arc.strong === 0) { + arc.weak++; + f?.(arc.value); + release(arc); + } } /** `Arc::clone`: the same value, counted once more. */ @@ -396,10 +423,15 @@ export class Weak implements Disposable { } [Symbol.dispose](): void { - this.arc.weak--; + if (--this.arc.weak === 0 && this.arc.strong === 0) unaddress(arcRoot(this.arc).value); } } +/** `Weak::as_ptr`, to an object: the object, or `Weak::new`'s address, `usize::MAX`. */ +export function weakAsPtr(weak: Weak): T { + return (weak.arc === DANGLING ? addressAt(0xffffffff) : weak.arc.value) as T; +} + /** `weak`, a `Weak`, unsized to a `Weak` of what `view` makes of its `Arc`. */ export function weakDyn(weak: Weak, view: (arc: Arc) => Arc): Weak { const dyn = new Weak(weak.arc as Arc, view as (arc: Arc) => Arc); diff --git a/ts/src/census.rs b/ts/src/census.rs index d064b37ad..b71001310 100644 --- a/ts/src/census.rs +++ b/ts/src/census.rs @@ -142,6 +142,9 @@ const VERBATIM: &[&str] = &[ ".await", "?", "Arc", + "Arc::into_inner", + "Arc::make_mut", + "Arc::new_cyclic", "Binary", "Buf::copy_to_bytes", "BufMut::put", @@ -166,6 +169,9 @@ const VERBATIM: &[&str] = &[ "Pending", "Pointer", "Poll", + "Rc", + "Rc::into_inner", + "Rc::make_mut", "Ready", "Reborrow", "Result", @@ -174,6 +180,8 @@ const VERBATIM: &[&str] = &[ "Vec::into_flattened", "Vec::spare_capacity_mut", "Vec::splice", + "Weak", + "Weak::as_ptr", "alloc", "array::from_ref", "async fn", diff --git a/ts/src/lower/address.rs b/ts/src/lower/address.rs new file mode 100644 index 000000000..d2c0c2815 --- /dev/null +++ b/ts/src/lower/address.rs @@ -0,0 +1,327 @@ +//! Addresses read as integers: `p as usize`, `addr`, and `expose_provenance`. +//! +//! Rust promises of an allocation's address only that it is aligned and that no other +//! live allocation overlaps it. JS frees an object only when it collects it, which no +//! program can see or time, so the one allocation JS frees as Rust does is an `Arc`'s or +//! `Rc`'s, when its strong and weak counts reach 0. `rs.addr` numbers each value an `Arc` +//! or `Rc` holds as an allocator places that allocation, and frees the number with it. A +//! pointer's address is then exact wherever its identity is, as +//! [`Translator::unidentified`] decides for comparing pointers, of a type the program keeps +//! only as an `Arc`'s or `Rc`'s value, which it never moves out of one. Nothing makes a +//! pointer of an address, which JS has no memory at. + +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def_id::DefId; +use rustc_middle::mir::{self, Operand, Rvalue, StatementKind, TerminatorKind}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_span::Span; + +use super::Translator; +use super::body::Body; +use super::library::Std; +use super::pointer::Pointee; +use crate::ts::Expr; + +/// The address of `p`, a pointer to `ty`, as a `usize`. +pub fn address<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, ty: Ty<'tcx>, span: Span) -> Expr { + match read(b, ty, span) { + Ok(layout) => Expr::rs("addr", [p].into_iter().chain(layout).collect()), + Err(refused) => refused, + } +} + +/// The size and alignment `rs.addr` reads the address of a pointer to `ty` by, or the +/// refusal of a type whose address JS has no counterpart of. +pub fn read<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, span: Span) -> Result<[Expr; 2], Expr> { + // Only an instance decides what a parameter is, and is refused on its own. + if !(b.t.polymorphic() && ty.has_param()) { + let why = match b.t.pointee(ty) { + _ if b.t.zero_sized(ty) => Some("which has no bytes and so no address of its own"), + Pointee::Array => Some("a view JS makes of its own of an array that may be in a larger one"), + _ if b.t.erased(ty) != ty => Some("which JS keeps as what it holds"), + _ => b.t.unidentified(ty), + }; + let what = format!("the address of a pointer to `{ty}`"); + if let Some(why) = why { + return Err(b.err(span, format!("{what}, {why}"))); + } + let read = b.t.sited(span, what); + b.t.addresses.push((ty, read.0, read.1)); + let stored = super::copy::storage(b.t.tcx, ty); + b.t.addressing.insert(stored); + } + Ok(layout(b.t, ty)) +} + +/// The size and alignment of a `ty`, as `rs.addr` takes them. +pub(super) fn layout<'tcx>(t: &Translator<'_, 'tcx>, ty: Ty<'tcx>) -> [Expr; 2] { + let (tcx, env) = (t.tcx, t.typing_env_pub()); + let layout = tcx.layout_of(env.as_query_input(ty)).ok(); + let size = layout.map_or(0, |l| l.size.bytes()); + let align = layout.map_or(1, |l| l.align.abi.bytes()); + [size, align].map(|n| Expr::lit(n.to_string())) +} + +/// `arc`, a new `Arc` or `Rc` of a `value`, given its address as it is made where the +/// runs before read the address of one, as an allocator places an allocation, so +/// allocations whose lives overlap have addresses apart even where a pointer is read only +/// once its allocation is freed. +pub fn numbered<'tcx>(b: &Body<'_, '_, 'tcx>, arc: Expr, value: Ty<'tcx>) -> Expr { + match b.t.addressed.contains(&super::copy::storage(b.t.tcx, value)) { + true => Expr::rs("arcAt", [arc].into_iter().chain(layout(b.t, value)).collect()), + false => arc, + } +} + +impl<'a, 'tcx> Translator<'a, 'tcx> { + /// Note `Arc::new_cyclic` of a `ty`, whose value JS has none of until its closure + /// returns, so a `Weak::as_ptr` of one has nothing to point at. + pub fn cyclic(&mut self, ty: Ty<'tcx>) { + self.cyclic.insert(self.tcx.erase_and_anonymize_regions(ty)); + } + + /// Note `Weak::as_ptr` of a `Weak` at `span`, refused once translation settles if + /// the crate makes a `ty` by `Arc::new_cyclic`. + pub fn weak_pointer(&mut self, ty: Ty<'tcx>, span: Span) { + let msg = format!( + "`Weak::as_ptr` of a `Weak<{ty}>`, which `Arc::new_cyclic` makes and JS has no value of until its closure returns" + ); + let (span, msg) = self.sited(span, msg); + self.weak_pointers + .push((self.tcx.erase_and_anonymize_regions(ty), span, msg)); + } + + /// Note `name`, such as `into_inner`, moving a `ty` out of its `Arc` or `Rc` at `span`, + /// refused once translation settles if the program reads the address of one. + pub fn arc_moved(&mut self, ty: Ty<'tcx>, name: &str, span: Span) { + let msg = format!( + "`{name}` of an `Arc` or `Rc` of `{ty}`, which moves it out of the allocation whose address the program reads" + ); + let (span, msg) = self.sited(span, msg); + self.arc_moves.push((super::copy::storage(self.tcx, ty), span, msg)); + } + + /// Refuse each address read of a type the program keeps anywhere but as an `Arc`'s or + /// `Rc`'s value, or writes whole in place, each move of one out of its `Arc`, and each + /// `Weak::as_ptr` of a value `Arc::new_cyclic` makes. + pub(super) fn refuse_addresses(&mut self) { + for (ty, span, msg) in std::mem::take(&mut self.weak_pointers) { + if self.cyclic.contains(&ty) { + self.refuse(span, msg); + } + } + let reads = std::mem::take(&mut self.addresses); + let moves = std::mem::take(&mut self.arc_moves); + if reads.is_empty() { + return; + } + let tcx = self.tcx; + let addressed: FxHashSet> = reads.iter().map(|&(ty, ..)| super::copy::storage(tcx, ty)).collect(); + let mut walk = Kept::new(tcx, &addressed); + walk.roots + .extend(self.writes.constants(tcx).map(|ty| (ty, Some(Holder::Static)))); + walk.roots + .extend(self.writes.kept(tcx).map(|ty| (ty, Some(Holder::Local)))); + // What the outside calls takes and returns whole, which JS keeps where it likes. + for instance in &self.roots { + let sig = self.sig(instance.def_id(), instance.args); + let fn_name = tcx.def_path_str(instance.def_id()); + walk.roots.extend( + sig.inputs_and_output + .iter() + .map(|ty| (ty, Some(Holder::Js(fn_name.clone())))), + ); + } + // Last, so first: what holds one by value names it before the local it came from. + walk.roots.extend(self.writes.locals(tcx).map(|ty| (ty, None))); + let kept = walk.run(); + for (ty, span, msg) in reads { + let ty = super::copy::storage(tcx, ty); + let why = match kept.get(&ty) { + Some(Holder::Type(holder)) => format!("which `{holder}` holds"), + Some(Holder::Local) => "which a local keeps that the program borrows, drops, or gives std".into(), + Some(Holder::Static) => "which a static or constant holds".into(), + Some(Holder::Js(name)) => format!("which JS keeps, as `{name}` takes or returns one"), + None if self.written_whole(ty) => { + "which the crate writes whole in place, moving another into its `Arc`".into() + } + None => continue, + }; + self.refuse( + span, + format!("{msg}, {why}, where only an `Arc`'s or `Rc`'s value has an address JS frees as Rust does"), + ); + } + for (ty, span, msg) in moves { + if addressed.contains(&ty) { + self.refuse(span, msg); + } + } + } +} + +/// What `body` keeps where the program may read its address, other than as an `Arc`'s or +/// `Rc`'s value: the type of each local it borrows whole, drops, or gives a function that +/// `own` says doesn't translate, but `Arc::new`, `Rc::new`, or their `pin`, and apart, +/// the type each constant reference points at, as a static's is. +pub(super) fn kept<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, + own: &dyn Fn(DefId) -> bool, +) -> (Vec>, Vec>) { + let mut locals = FxHashSet::default(); + let mut constants = Vec::new(); + let constant = |operand: &Operand<'tcx>, constants: &mut Vec>| { + if let Operand::Constant(c) = operand + && let ty::Ref(_, pointee, _) | ty::RawPtr(pointee, _) = *c.ty().kind() + { + constants.push(pointee); + } + }; + for data in body.basic_blocks.iter() { + for stmt in &data.statements { + let StatementKind::Assign(assign) = &stmt.kind else { + continue; + }; + match &assign.1 { + Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) if let Some(local) = place.as_local() => { + locals.insert(local); + } + Rvalue::Use(op, _) | Rvalue::Cast(_, op, _) | Rvalue::UnaryOp(_, op) => constant(op, &mut constants), + Rvalue::Aggregate(_, ops) => ops.iter().for_each(|op| constant(op, &mut constants)), + _ => {} + } + } + match &data.terminator().kind { + TerminatorKind::Drop { place, .. } if let Some(local) = place.as_local() => { + locals.insert(local); + } + TerminatorKind::Call { func, args, .. } => { + args.iter().for_each(|arg| constant(&arg.node, &mut constants)); + let env = ty::TypingEnv::post_analysis(tcx, body.source.def_id()); + let given = match func.const_fn_def() { + Some((def_id, args)) + if let Ok(Some(instance)) = ty::Instance::try_resolve(tcx, env, def_id, args) + && let ty::InstanceKind::Item(did) = instance.def + && own(did) => + { + false + } + Some((def_id, _)) => !matches!( + super::library::key(tcx, def_id), + Some((owner, name)) if matches!(owner.as_str(), "Arc" | "Rc") && matches!(name.as_str(), "new" | "pin") + ), + None => true, + }; + if given { + locals.extend(args.iter().filter_map(|arg| arg.node.place()?.as_local())); + } + } + _ => {} + } + } + let locals = locals.into_iter().map(|local| body.local_decls[local].ty).collect(); + (locals, constants) +} + +/// Where the program keeps a value of an addressed type other than as an `Arc`'s or +/// `Rc`'s value. +#[derive(Clone)] +enum Holder<'tcx> { + /// By value in a value of this type: a field, an element, a payload, or a capture, or + /// in a `Box` or a collection. + Type(Ty<'tcx>), + /// In a local a body borrows, drops, or gives std. + Local, + /// In a static or constant. + Static, + /// Where JS keeps what the function named takes or returns. + Js(String), +} + +/// The addressed types each value a program keeps holds where they have no address JS +/// frees as Rust does, walking what each holds by value and points at. +struct Kept<'s, 'tcx> { + tcx: TyCtxt<'tcx>, + addressed: &'s FxHashSet>, + /// The values still to look in, each with what holds it, or `None` where it is a + /// local, an `Arc`'s or `Rc`'s value, or what a reference points at, which hold it + /// where an address may be its own. + roots: Vec<(Ty<'tcx>, Option>)>, + seen: FxHashSet>, + found: FxHashMap, Holder<'tcx>>, +} + +impl<'s, 'tcx> Kept<'s, 'tcx> { + fn new(tcx: TyCtxt<'tcx>, addressed: &'s FxHashSet>) -> Self { + Kept { + tcx, + addressed, + roots: Vec::new(), + seen: FxHashSet::default(), + found: FxHashMap::default(), + } + } + + fn run(mut self) -> FxHashMap, Holder<'tcx>> { + let tcx = self.tcx; + let env = ty::TypingEnv::fully_monomorphized(); + while let Some((ty, holder)) = self.roots.pop() { + let ty = tcx + .try_normalize_erasing_regions(env, ty::Unnormalized::new_wip(ty)) + .unwrap_or(ty); + let ty = tcx.erase_and_anonymize_regions(ty); + if let Some(holder) = &holder + && self.addressed.contains(&super::copy::storage(tcx, ty)) + { + self.found + .entry(super::copy::storage(tcx, ty)) + .or_insert_with(|| holder.clone()); + } + // What a type holds is held alike wherever the type is. + if !self.seen.insert(ty) { + continue; + } + let held = Some(Holder::Type(ty)); + match *ty.kind() { + ty::Array(elem, _) | ty::Slice(elem) | ty::Pat(elem, _) => self.roots.push((elem, held)), + ty::Tuple(tys) => self.roots.extend(tys.iter().map(|t| (t, held.clone()))), + ty::Ref(_, pointee, _) | ty::RawPtr(pointee, _) => self.roots.push((pointee, None)), + ty::Adt(adt, args) if super::library::is_std(tcx, adt.did()) => match Std::of(tcx, adt.did()) { + // The value of its own allocation, which JS frees as Rust does. + Some(Std::Arc | Std::Weak | Std::NonNull) => self.roots.push((args.type_at(0), None)), + _ if adt.is_phantom_data() => {} + // Each of its type arguments, by value or in a `Box` or a collection. + _ => self.roots.extend(args.types().map(|t| (t, held.clone()))), + }, + ty::Adt(adt, args) => self.roots.extend( + adt.all_fields() + .map(|f| (f.ty(tcx, args).skip_norm_wip(), held.clone())), + ), + ty::Closure(_, args) => self + .roots + .extend(args.as_closure().upvar_tys().iter().map(|t| (t, held.clone()))), + ty::CoroutineClosure(_, args) => self.roots.extend( + args.as_coroutine_closure() + .upvar_tys() + .iter() + .map(|t| (t, held.clone())), + ), + // Its captures, and the locals it keeps across an `.await`. + ty::Coroutine(def_id, args) => { + self.roots + .extend(args.as_coroutine().upvar_tys().iter().map(|t| (t, held.clone()))); + if let Ok(layout) = tcx.coroutine_layout(def_id, args) { + for f in layout.field_tys.iter() { + let saved = ty::EarlyBinder::bind(tcx, f.ty); + let saved = tcx.instantiate_and_normalize_erasing_regions(args, env, saved); + self.roots.push((saved, held.clone())); + } + } + } + _ => {} + } + } + self.found + } +} diff --git a/ts/src/lower/collections.rs b/ts/src/lower/collections.rs index b70094fdb..dc1c16a15 100644 --- a/ts/src/lower/collections.rs +++ b/ts/src/lower/collections.rs @@ -3089,10 +3089,10 @@ pub fn cmp_fn<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, partial: bool, spa let cmp = trait_fn(tcx, "Ord", "cmp"); b.fn_value(cmp, tcx.mk_args(&[ty.into()]), span) } - _ if raw_pointee(tcx, ty).is_some() => b.err( - span, - format!("ordering `{ty}` by its address, which JS has no counterpart of"), - ), + _ if let Some(pointee) = raw_pointee(tcx, ty) => match super::address::read(b, pointee, span) { + Ok(layout) => Expr::rs("cmpAddr", layout.into()), + Err(refused) => refused, + }, // Each instance decides what its type parameters are. _ if b.t.deferred(ty) => Expr::undefined(), _ => b.err(span, format!("ordering `{ty}`, which is not supported yet")), @@ -3403,10 +3403,10 @@ fn hash_fn<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, hasher: Ty<'tcx>, spa false => f, } } - _ if raw_pointee(tcx, ty).is_some() => b.err( - span, - format!("hashing `{ty}` by its address, which JS has no counterpart of"), - ), + _ if let Some(pointee) = raw_pointee(tcx, ty) => match super::address::read(b, pointee, span) { + Ok(layout) => Expr::rs("hashAddr", layout.into()), + Err(refused) => refused, + }, _ if b.t.deferred(ty) => Expr::undefined(), _ => b.err(span, format!("hashing `{ty}` as a key, which is not supported yet")), } diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 92ca19873..d2dd0207a 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -92,6 +92,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { .is_some_and(|holders| writes.contains(holders, false, self.tcx, ty)) } + /// Whether the crate may write a whole `ty`, or a value holding one, in place. + pub fn written_whole(&self, ty: Ty<'tcx>) -> bool { + self.writes.contains(&self.writes.replaced, true, self.tcx, ty) + } + /// Whether a `ty` is, or holds by value at any depth, a part that `object` says is a JS /// object, into which a raw pointer may be made through a reference that the crate's /// code can't follow back to where it was borrowed, by any body or by `body` alone. A @@ -467,6 +472,14 @@ pub struct Writes<'tcx> { /// The arguments of each body's instances that translation reached, without type /// parameters. reached: FxHashMap>>, + /// The type of each local of each body read, by that body: every allocation the + /// crate's code makes is one, or what one points at. + locals: FxIndexSet<(DefId, Ty<'tcx>)>, + /// The locals each body read keeps where their address may be read, by that body, as + /// [`super::address::kept`] finds them. + kept: FxIndexSet<(DefId, Ty<'tcx>)>, + /// What the constant references of each body read point at, by that body. + constants: FxIndexSet<(DefId, Ty<'tcx>)>, } /// Types, each without type parameters or over those of the body naming it. @@ -511,6 +524,18 @@ impl<'tcx> Writes<'tcx> { // One without MIR, such as an import, is refused where it is reached. _ => continue, }; + for local in body.local_decls.iter() { + writes + .locals + .insert((def_id, tcx.erase_and_anonymize_regions(local.ty))); + } + let (kept, constants) = super::address::kept(tcx, body, own); + for ty in kept { + writes.kept.insert((def_id, tcx.erase_and_anonymize_regions(ty))); + } + for ty in constants { + writes.constants.insert((def_id, tcx.erase_and_anonymize_regions(ty))); + } // Another crate lists none of its closures' bodies. for local in body.local_decls.iter() { queue.extend(local.ty.walk().filter_map(|arg| match *arg.as_type()?.kind() { @@ -553,6 +578,45 @@ impl<'tcx> Writes<'tcx> { .is_some_and(|args| args.contains(&instance.args)) } + /// The type of each local of the bodies read, one over type parameters for each + /// instance translation reached of its body. + pub fn locals(&self, tcx: TyCtxt<'tcx>) -> impl Iterator> { + self.instances(tcx, &self.locals) + } + + /// The locals the bodies read keep where their address may be read, as + /// [`Self::locals`] are. + pub fn kept(&self, tcx: TyCtxt<'tcx>) -> impl Iterator> { + self.instances(tcx, &self.kept) + } + + /// What the constant references of the bodies read point at, as [`Self::locals`] are. + pub fn constants(&self, tcx: TyCtxt<'tcx>) -> impl Iterator> { + self.instances(tcx, &self.constants) + } + + /// Each of `types`, by the body naming it, one over type parameters for each instance + /// translation reached of its body. + fn instances<'s>( + &'s self, + tcx: TyCtxt<'tcx>, + types: &'s FxIndexSet<(DefId, Ty<'tcx>)>, + ) -> impl Iterator> + 's { + let env = ty::TypingEnv::fully_monomorphized(); + types.iter().flat_map(move |&(def_id, ty)| { + let instances: Vec> = match ty.has_param() { + false => vec![ty], + true => (self.reached.get(&def_id).into_iter().flatten()) + .filter_map(|&args| { + tcx.try_instantiate_and_normalize_erasing_regions(args, env, EarlyBinder::bind(tcx, ty)) + .ok() + }) + .collect(), + }; + instances + }) + } + /// Whether `ty` is one of `types`, or with `held`, a type one of them holds by value. /// Over type parameters, as a census lowers some functions, it may be. A write into a /// type parameter counts for the type each reached instance gives it: in a census, a @@ -598,7 +662,7 @@ impl<'tcx> Types<'tcx> { /// `ty` as [`Types`] keys it: without regions, a `MaybeUninit` or `ManuallyDrop` as what /// it holds, and an array as a slice of its element. -fn storage<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { +pub(super) fn storage<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { let mut ty = tcx.erase_and_anonymize_regions(ty); while let ty::Adt(adt, args) = ty.kind() && matches!(Std::of(tcx, adt.did()), Some(Std::MaybeUninit | Std::ManuallyDrop)) @@ -614,7 +678,11 @@ fn storage<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { /// `ty` and each type it holds by value, at any depth: in a field, an element, or a /// payload. An array of no elements holds none, and a type parameter, or a type `env` /// can't normalize, stands for what it holds. -fn by_value<'tcx>(tcx: TyCtxt<'tcx>, env: ty::TypingEnv<'tcx>, ty: Ty<'tcx>) -> impl Iterator> { +pub(super) fn by_value<'tcx>( + tcx: TyCtxt<'tcx>, + env: ty::TypingEnv<'tcx>, + ty: Ty<'tcx>, +) -> impl Iterator> { let mut held = FxIndexSet::default(); let mut stack = vec![ty]; while let Some(ty) = stack.pop() { diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 17c2ec197..cb1e9cb60 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -1665,7 +1665,8 @@ fn mapping<'tcx>( || args.type_at(0).is_sized(tcx, b.t.typing_env())) && !super::byte_array(args.type_at(0)) { - return Some(Expr::New(Box::new(Expr::Rs("Arc".into())), vec![value])); + let arc = Expr::New(Box::new(Expr::Rs("Arc".into())), vec![value]); + return Some(super::address::numbered(b, arc, args.type_at(0))); } // An error, boxed as a `dyn Error`, which `?` converts it by. One of a string boxes // std's own private error type. @@ -4180,7 +4181,10 @@ pub fn default_value<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, span: Span, } } Std::Box | Std::Wrapping | Std::Saturating => part(b, args.type_at(0), out), - Std::Arc => Expr::New(Box::new(Expr::Rs("Arc".into())), vec![part(b, args.type_at(0), out)]), + Std::Arc => { + let arc = Expr::New(Box::new(Expr::Rs("Arc".into())), vec![part(b, args.type_at(0), out)]); + super::address::numbered(b, arc, args.type_at(0)) + } Std::Mutex => Expr::New(Box::new(Expr::Rs("Mutex".into())), vec![part(b, args.type_at(0), out)]), Std::RefCell => Expr::New( Box::new(Expr::Rs("RefCell".into())), diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 6d127f34b..c44e3dd7b 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -214,6 +214,25 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { _ => same.not(), }); } + // Ordered by their addresses. + if let ty::RawPtr(pointee, _) = lty.kind() + && let Some(js) = match op { + BinOp::Lt => Some("<"), + BinOp::Le => Some("<="), + BinOp::Gt => Some(">"), + BinOp::Ge => Some(">="), + BinOp::Cmp => Some("cmp"), + _ => None, + } { + let x = self.operand(l, span, out); + let x = crate::lower::address::address(self.b, x, *pointee, span); + let y = self.operand(r, span, out); + let y = crate::lower::address::address(self.b, y, *pointee, span); + return Some(match js { + "cmp" => Expr::rs("cmp", vec![x, y]), + _ => x.binary(js, y), + }); + } if matches!(op, BinOp::Cmp | BinOp::Offset) { return Some(self.err(span, format!("`{op:?}` is not supported"))); } @@ -484,6 +503,15 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { span, format!("a cast from `{from}` to `{to}`, which reads the bytes of one type as another"), ), + CastKind::PointerExposeProvenance if let ty::RawPtr(pointee, _) = from.kind() => { + let p = self.operand(op, span, out); + let address = crate::lower::address::address(self.b, p, *pointee, span); + let address = num::Operand { + expr: address, + value: None, + }; + num::cast(self.b, address, tcx.types.usize, to, span) + } CastKind::PointerExposeProvenance | CastKind::PointerWithExposedProvenance => self.err( span, format!("a cast from `{from}` to `{to}`, between a pointer and an integer"), diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 3ce9abc4c..924425693 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -5,6 +5,7 @@ //! call is always a direct call. Types are translated once, generically, and a function of //! a type is a module-level one taking `self` first, so a bundler drops what goes unused. +pub mod address; mod alloc; mod bind; pub use bind::check_module; @@ -219,6 +220,17 @@ pub struct Translator<'a, 'tcx> { capacity_reads: Vec<(Span, String)>, /// Whether a crate makes a `Bytes` or a `Vec` of a `String`'s bytes, whose capacity JS keeps none of. string_bytes: bool, + /// Each read of an address, of a pointer to its type, refused once translation settles + /// if the program keeps that type anywhere but as an `Arc`'s value, as [`address`] says. + addresses: Vec<(Ty<'tcx>, Span, String)>, + /// Each move of a value out of its `Arc` or `Rc`, by its type, refused once translation + /// settles if the program reads the address of one. + arc_moves: Vec<(Ty<'tcx>, Span, String)>, + /// The types `Arc::new_cyclic` makes. + cyclic: rustc_data_structures::fx::FxHashSet>, + /// Each `Weak::as_ptr`, by the type it points at, refused once translation settles if + /// `Arc::new_cyclic` makes one. + weak_pointers: Vec<(Ty<'tcx>, Span, String)>, /// Whether each type's drop does something JS can see, as [`Self::drops`] found. drops: rustc_data_structures::fx::FxHashMap, bool>, /// The types [`Self::drops`] is still deciding, and whether the query open now read one. @@ -255,6 +267,11 @@ pub struct Translator<'a, 'tcx> { compared: rustc_data_structures::fx::FxHashSet>, /// Those this run compared. comparing: rustc_data_structures::fx::FxHashSet>, + /// The types whose addresses the runs before read, by their storage, whose `Arc`s are + /// given their addresses as they are made. + addressed: rustc_data_structures::fx::FxHashSet>, + /// Those this run read. + addressing: rustc_data_structures::fx::FxHashSet>, /// Whether the runs before reached a `panic::set_hook`, so every panic carries the /// `rs.Location` its hook reads. pub hooked: bool, @@ -410,6 +427,21 @@ impl Erased { } } +/// What the runs before found, which the next judges by. +#[derive(Default)] +struct Before<'tcx> { + /// The instances whose bodies' writes they read. + read: rustc_data_structures::fx::FxHashSet>, + /// The closures cloned where they can't share. + cloned: rustc_data_structures::fx::FxHashSet, + /// The types whose pointers they compared. + compared: rustc_data_structures::fx::FxHashSet>, + /// The types whose addresses they read. + addressed: rustc_data_structures::fx::FxHashSet>, + /// Whether they reached a `panic::set_hook`. + hooked: bool, +} + impl<'a, 'tcx> Translator<'a, 'tcx> { /// A translator that has run `reach`, with what it copies and refuses settled. Whether /// code writes into a type in place decides that, and of another crate's code, only what @@ -418,7 +450,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// writes it didn't read, or an instance of one writing a type parameter that it didn't /// judge by. A closure's clone may come before or after the closure is made, which /// makes it as its clones need, so it runs again too when it reaches a new one, and a - /// comparison of pointers may come before or after the borrows and copies it decides. + /// comparison of pointers may come before or after the borrows and copies it decides, + /// and an address read before or after the `Arc`s it numbers as they are made. /// Once it reaches a `panic::set_hook`, it runs again with every panic located. fn settle( tcx: TyCtxt<'tcx>, @@ -426,12 +459,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { census: bool, mut reach: impl FnMut(&mut Self) -> R, ) -> Option<(Self, R)> { - let mut read = rustc_data_structures::fx::FxHashSet::default(); - let mut cloned = rustc_data_structures::fx::FxHashSet::default(); - let mut compared = rustc_data_structures::fx::FxHashSet::default(); - let mut hooked = false; + let mut before = Before::default(); loop { - let mut t = Self::new(tcx, config, census, read, cloned, compared, hooked)?; + let mut t = Self::new(tcx, config, census, before)?; t.bind(); let out = reach(&mut t); let reached: Vec> = t @@ -444,18 +474,29 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if reached.is_empty() && t.cloning.is_subset(&t.cloned) && t.comparing.is_subset(&t.compared) + && t.addressing.is_subset(&t.addressed) && (t.hooked || !t.hooking) { t.refuse_capacity_reads(); + t.refuse_addresses(); return Some((t, out)); } - read = std::mem::take(&mut t.read); - read.extend(reached); - cloned = std::mem::take(&mut t.cloned); - cloned.extend(t.cloning.drain()); - compared = std::mem::take(&mut t.compared); - compared.extend(t.comparing.drain()); - hooked |= t.hooking; + before = Before { + read: std::mem::take(&mut t.read).into_iter().chain(reached).collect(), + cloned: std::mem::take(&mut t.cloned) + .into_iter() + .chain(t.cloning.drain()) + .collect(), + compared: std::mem::take(&mut t.compared) + .into_iter() + .chain(t.comparing.drain()) + .collect(), + addressed: std::mem::take(&mut t.addressed) + .into_iter() + .chain(t.addressing.drain()) + .collect(), + hooked: t.hooked || t.hooking, + }; } } @@ -475,15 +516,14 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { self.cloned.contains(&def_id) } - fn new( - tcx: TyCtxt<'tcx>, - config: &'a Config, - census: bool, - read: rustc_data_structures::fx::FxHashSet>, - cloned: rustc_data_structures::fx::FxHashSet, - compared: rustc_data_structures::fx::FxHashSet>, - hooked: bool, - ) -> Option { + fn new(tcx: TyCtxt<'tcx>, config: &'a Config, census: bool, before: Before<'tcx>) -> Option { + let Before { + read, + cloned, + compared, + addressed, + hooked, + } = before; if tcx.data_layout.pointer_size().bits() != 32 { tcx.dcx().err( "rust2-ts: translation needs a 32-bit target, such as wasm32-unknown-unknown, so usize is a number", @@ -507,6 +547,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { refusals: Some(Vec::new()), capacity_reads: Vec::new(), string_bytes: false, + addresses: Vec::new(), + arc_moves: Vec::new(), + cyclic: Default::default(), + weak_pointers: Vec::new(), drops: Default::default(), drops_open: Default::default(), drops_cycle: false, @@ -530,6 +574,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { cloning: Default::default(), compared, comparing: Default::default(), + addressed, + addressing: Default::default(), hooked, hooking: false, held: false, diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 087e48216..50fe837e1 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -101,18 +101,21 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// where a value of no bytes is unspecified, but a pointer to `()` is an identity. Notes /// the comparison of objects, for [`Self::taken`]. pub fn incomparable(&mut self, ty: Ty<'tcx>) -> Option { + self.unidentified(ty) + .map(|why| format!("comparing pointers to `{ty}`, {why}")) + } + + /// Why pointers to `ty` that are not null aren't identities JS keeps, as + /// [`Self::incomparable`] says, noting the comparison of those that are. + pub fn unidentified(&mut self, ty: Ty<'tcx>) -> Option<&'static str> { if self.identity_pointee(ty) { return None; } if self.zero_sized(ty) { - return Some(format!( - "comparing pointers to `{ty}`, which has no bytes and so no address of its own" - )); + return Some("which has no bytes and so no address of its own"); } if ty.is_trait() { - return Some(format!( - "comparing pointers to `{ty}`, each an object JS makes of its own as it unsizes" - )); + return Some("each an object JS makes of its own as it unsizes"); } match self.pointee(ty) { // `from_ref`, `from_mut` of a variable, and `Option::as_slice` make an array of @@ -121,26 +124,24 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if let ty::Array(_, len) = self.uninit_inner(ty).kind() && len.try_to_target_usize(self.tcx).is_none_or(|n| n == 1) => { - Some(format!( - "comparing pointers to `{ty}`, an array of one JS may make of its own for a value, as `array::from_ref` does" - )) + Some("an array of one JS may make of its own for a value, as `array::from_ref` does") } Pointee::Object | Pointee::Array => { self.compare(ty); None } - Pointee::Place => Some(format!( - "comparing pointers to `{ty}`, each a place JS makes of its own" - )), - Pointee::Slice => Some(format!( - "comparing pointers to `{ty}`, whose element pointers are places JS makes of their own" - )), + Pointee::Place => Some("each a place JS makes of its own"), + Pointee::Slice => Some("whose element pointers are places JS makes of their own"), } } /// What tells whether two pointers to `ty` point at the same place where `===` can't: - /// the slices' of an array. + /// the slices' of an array, and the addresses of a type whose address the runs before + /// read, which a pointer to a freed allocation shares with the one that reuses it. pub fn same_fn(&self, ty: Ty<'tcx>) -> Option { + if self.addressed.contains(&super::copy::storage(self.tcx, ty)) { + return Some(Expr::rs("sameAddr", super::address::layout(self, ty).into())); + } (self.pointee(ty) == Pointee::Array).then(|| self.array_rs(ty, "Same")) } @@ -569,8 +570,9 @@ pub fn call<'tcx>( let p = arg(); cast(b, p, pointee(), targ(gargs, 1), span) } - ("ptr" | "NonNull", "addr" | "expose_provenance" | "with_addr" | "map_addr" | "without_provenance") => { - b.err(span, "a pointer's address, which JS has no counterpart of") + ("ptr" | "NonNull", "addr" | "expose_provenance") => super::address::address(b, arg(), pointee(), span), + ("ptr" | "NonNull", "with_addr" | "map_addr" | "without_provenance") => { + b.err(span, "a pointer made of an address, which JS has no memory at") } // A raw pointer to a slice is null where its first element's is. ("ptr", "is_null") if kind(b, pointee()) == Some(Pointee::Slice) => { @@ -697,6 +699,23 @@ pub fn call<'tcx>( }; Expr::rs("arcRaw", vec![arc, p, Expr::Str(owner.into())]) } + // The object a `Weak` points at, there still once the last `Arc` drops it, as its + // allocation is. + ("Weak", "as_ptr") + if kind(b, pointee()) == Some(Pointee::Object) + && !pointee().is_trait() + && !super::dynamic::unsized_object(b.t.tcx, pointee()) => + { + b.t.weak_pointer(pointee(), span); + Expr::rs("weakAsPtr", vec![arg()]) + } + ("Weak", "as_ptr") => b.err( + span, + format!( + "a raw pointer to the `{}` a `Weak` holds, which JS keeps no object of", + pointee() + ), + ), ("Arc" | "Rc", "from_raw") => Expr::rs("arcFromRaw", vec![arg(), Expr::Str(owner.into())]), ("Arc" | "Rc", "increment_strong_count") => { let arc = Expr::rs("arcFromRaw", vec![arg(), Expr::Str(owner.into())]); diff --git a/ts/src/lower/task.rs b/ts/src/lower/task.rs index e46390a12..678273aa9 100644 --- a/ts/src/lower/task.rs +++ b/ts/src/lower/task.rs @@ -137,10 +137,15 @@ pub fn call<'tcx>( // A clone of a reference is that reference, not a clone of what it points at. let owned_std = self_std.filter(|_| self_ty.is_some_and(|t| !t.is_ref())); let e = match (owner, name) { - ("Arc" | "Rc", "new") => Expr::New(Box::new(Expr::Rs("Arc".into())), args), - ("Arc" | "Rc", "new_cyclic") => Expr::rs("arcNewCyclic", args), // JS erases the pin. - ("Arc" | "Rc", "pin") => Expr::New(Box::new(Expr::Rs("Arc".into())), args), + ("Arc" | "Rc", "new" | "pin") => { + let arc = Expr::New(Box::new(Expr::Rs("Arc".into())), args); + super::address::numbered(b, arc, call.args.type_at(0)) + } + ("Arc" | "Rc", "new_cyclic") => { + b.t.cyclic(call.args.type_at(0)); + super::address::numbered(b, Expr::rs("arcNewCyclic", args), call.args.type_at(0)) + } // The object of one of a trait object, which every pointer to it shares. A `dyn Any`'s // may hold a copy of a value JS copies, which `Arc::downcast` would not see written. ("Arc" | "Rc", "get_mut") @@ -160,6 +165,7 @@ pub fn call<'tcx>( match name { "get_mut" => Expr::rs(format!("arcGetMut{place}"), args), _ => { + b.t.arc_moved(value, &format!("{owner}::make_mut"), call.span); let cloned = shared_box(tcx, value).unwrap_or(value); let clone = super::collections::clone_fn(b, cloned, call.span); let arc = b.t.sig(call.def_id, call.args).inputs()[0].peel_refs(); @@ -218,6 +224,7 @@ pub fn call<'tcx>( ("Arc" | "Rc", "downgrade") | ("Weak", "new") => Expr::New(Box::new(Expr::Rs("Weak".into())), args), // The value of the last strong one, which its count says, and else its `None`. ("Arc" | "Rc", "into_inner") => { + b.t.arc_moved(call.args.type_at(0), &format!("{owner}::into_inner"), call.span); let none = b.t.none_arg(call.args.type_at(0)); Expr::rs("arcIntoInner", args.into_iter().chain(none).collect()) } diff --git a/ts/tests/pointers.ts b/ts/tests/pointers.ts index 77476adaa..530de814d 100644 --- a/ts/tests/pointers.ts +++ b/ts/tests/pointers.ts @@ -131,3 +131,40 @@ const flagAt = rs.arcRaw(flag, rs.arcPlace(flag), "Arc"); assert.equal(rs.arcErase(rs.ptrAdd(rs.ptrAdd(flagAt, 1), -1), "bool", 1), flagAt); assert.equal(rs.arcErase(rs.NULL, "bool", 1), rs.NULL); assert.throws(() => rs.arcErase({ v: false }, "bool", 1), /only an `Arc`'s or `Rc`'s raw pointer keeps the place of/); + +// An `Arc`'s value's address, given as it is made and freed with the allocation once its +// strong and weak counts reach 0, which the next of its size reuses, as an allocator does, +// so churning far past wasm32's 4 GiB never runs out. One a `Weak` holds stays apart from +// every other. +const MIB = 2 ** 20; +const made = (n: number) => rs.arcAt(new rs.Arc({ n }), MIB, 8); +const churned = made(0); +const churnedAt = rs.addr(churned.value, MIB, 8); +churned[Symbol.dispose](); +for (let n = 1; n <= 10_000; n++) { + const arc = made(n); + assert.equal(rs.addr(arc.value, MIB, 8), churnedAt); + arc[Symbol.dispose](); +} +const weakly = made(0); +const weak = new rs.Weak(weakly); +const weaklyAt = rs.addr(weakly.value, MIB, 8); +weakly[Symbol.dispose](); +const beside = made(1); +assert.notEqual(rs.addr(beside.value, MIB, 8), weaklyAt); +assert.equal(rs.addr(rs.weakAsPtr(weak), MIB, 8), weaklyAt); +weak[Symbol.dispose](); +assert.equal(rs.addr(made(2).value, MIB, 8), weaklyAt); +// Pointers read only once their allocations are freed keep the addresses they had: apart +// where the two lived at once, and never holding a block, so 10,000 more run out of nothing. +const [first, second] = [made(3), made(4)]; +first[Symbol.dispose](); +second[Symbol.dispose](); +assert.notEqual(rs.addr(first.value, MIB, 8), rs.addr(second.value, MIB, 8)); +const freed = []; +for (let n = 0; n < 10_000; n++) { + const arc = made(n); + arc[Symbol.dispose](); + freed.push(arc.value); +} +for (const value of freed) rs.addr(value, MIB, 8);