diff --git a/quest/m3/README.md b/quest/m3/README.md index 70d7ed42a..7ad820377 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -34,7 +34,6 @@ generic std gap is planned here even when moq never calls it. - [An Arc's pointer](/quest/m3/ts-arc-pointers.md) - `Arc::as_ptr` and `Rc::as_ptr` translate, comparable by `ptr::addr_eq` - [The rest of a vector's methods](/quest/m3/ts-vec-rest.md) - `append`, `resize`, `split_off`, `try_reserve`, and `Vec::from(VecDeque)` translate - [A reversed zip's per-pair cost](/quest/m3/ts-zip-rev-overhead.md) - a reversed by-index zip costs per pair what a forward one does, or the gap is explained -- [Whole writes into a lazy cell or thread-local key](/quest/m3/ts-lazy-writes.md) - a whole write through a `&mut` to a `LazyCell`, `LazyLock`, or `LocalKey` keeps its state - [Type errors at the Rust that made them](/quest/m3/ts-span-precision.md) - a `tsc` error on output names the tail expression or call argument that made it - [Fresh writes through a zip, a copy, and a repeat](/quest/m3/ts-fresh-shapes.md) - zipping a `Vec<[T; N]>`, reading a `T` out of `Zip::next`, and `copied` or `vec![x; n]` over a `Copy` type translate - [Python's wake on the loop's own thread](/quest/m3/ffi-python-wake.md) - a wake on the event loop's thread resumes the future with no socket pair diff --git a/quest/m3/ts-lazy-writes.md b/quest/m3/ts-lazy-writes.md deleted file mode 100644 index d431b6860..000000000 --- a/quest/m3/ts-lazy-writes.md +++ /dev/null @@ -1,19 +0,0 @@ -# [S] Whole writes into a lazy cell or thread-local key - -## Goal - -[rust2-ts](/ts/README.md) translates a whole write through a `&mut` to a `LazyCell`, a -`LazyLock`, or a `LocalKey`, as `*r = LazyCell::new(f)` or `mem::swap` of two, keeping -whether each is initialized and what it holds, proven by differential tests against native -Rust that read each after the write. - -## Plan - -Found by [#386](https://github.com/moq-dev/rust2/pull/386), which refuses these writes with -a span in `Translator::write_in`: their runtime classes keep their state in `#private` -fields, which the field copy a whole write lowers to misses, so the write was silently -lost on `main` before. Copy that state across in the runtime (a method each class owns), -or write the object in place, rather than widening the field copy. `PollFn` is refused the -same way; fold it in if the same method fits. - -Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index cd93ac047..916465519 100644 --- a/ts/README.md +++ b/ts/README.md @@ -106,8 +106,8 @@ crate translation reaches is refused, and so are two versions of one crate it re | `ControlFlow` | `rt.Result`, `Ok` to continue; what `?` branches on is the value it was applied to, but for a `Poll`'s `Pending` and `Ready(None)`, which continue as `Ok` of themselves | | tuple, `[T; N]`, `&[T]`, `Box<[T]>`, `Vec` | an array; for bytes, `Uint8Array` for `[u8; N]`, `rt.ByteSlice` for `&[u8]`, `&[u8; N]`, and `Box<[u8]>`, and `rt.ByteVec` for `Vec` | | `Cow<[T]>` | `rt.Cow`: the `Vec` it owns, or an `rt.Borrowed` holding the slice it borrows; `rt.ByteCow` for bytes, an `rt.ByteVec` or an `rt.ByteSlice` | -| `&T`, `&mut T`, `Box`, `Reverse`, `NonZero` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, a `String`, or a reference, or to an `Arc` or a `Waker`, is an `rt.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 `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.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 `rt.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; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, 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 | +| `&T`, `&mut T`, `Box`, `Reverse`, `NonZero` | 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 `rt.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 `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.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 `rt.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; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rt` class such as `rt.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | | `MaybeUninit` | `T`: `undefined` until written, or an object whose parts kept as objects are there, holding `undefined` | | `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 | @@ -135,7 +135,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Arc`, `Rc`, `Weak`, `Mutex` | `rt.Arc` for either, of what a `Box` is for a slice or `str`, with its strong and weak counts, an `Arc<[u8]>` of an `Arc`'s bytes sharing its count, and `new_cyclic`'s `Weak` upgrading only once its value is made, `get_mut` lending its value only while no other `Arc` or `Weak` shares it, `make_mut` cloning it into a new `rt.Arc` while another `Arc` shares it, or moving it there while only `Weak`s do, which then dangle, and `into_inner` giving it only from the last strong `Arc`, after which its `Weak`s no longer upgrade; `rt.Weak`, counted in its `rt.Arc` until it drops, and of no `Arc` once made by `Weak::new`; `rt.Mutex`, its own guard, which a lock gives as `Ok` and the guard's drop unlocks: locking it again while held panics and `try_lock` gives `WouldBlock`, as wasm32's do, and a guard's `map` and `filter_map` make `rt.MutexMap`s, each where the part it lends is, which hold the mutex locked until they drop, and a panic aborts, so nothing poisons it and `thread::panicking` is `false`; a `Mutex`'s, a `RefCell`'s, and an `RwLock`'s `into_inner` gives its value and `get_mut` lends it, or an `rt.Place` of it where JS copies it | | `PoisonError`, `TryLockError` | the `T` it holds; `rt.TryLockError`, tagged as `Bound` is | | `Cell`, `UnsafeCell`, `RefCell`, `RwLock` | `rt.Cell`, the place holding its value in `v`, for either of the first two: `UnsafeCell::get`'s pointer is the cell where it is an `rt.Place`, and otherwise the object in `v`, refused where the crate may put another cell or object in its place; `rt.RefCell` for either of the others, its own guard, counting its borrows: borrowing it while that conflicts panics as `RefCell`'s does, and locking it so aborts as wasm32's `RwLock` does; `try_borrow` and `try_borrow_mut` fail with `rt.BorrowError` and `rt.BorrowMutError` instead, a clone borrows the cell, `Ref::clone` counts another borrow of it, and a guard's `map`, `filter_map`, `try_map`, and `map_split`, an `RwLock` guard's included, make `rt.RefMap`s, each where a part it lends is, which a write through a mapped `RefMut` reaches in place, sharing the cell's borrow until the last drops; `RwLockWriteGuard::downgrade` keeps the lock as one read, and `Ref::leak` and `RefMut::leak` keep the borrow until `RefCell::undo_leak` ends it; `Cell::update` calls its function on a copy, and `RefCell::replace_with` on the value, borrowed as `borrow_mut` does, or its place where JS copies it; refused for a part of a sized type JS can't write in place, such as a binding's | -| `thread_local!`, `LocalKey` | an `rt.LocalKey`, a module's value, which makes its value on first use from the `const` or the expression and never drops it, as wasm32's do; `with` calls its function on that value, `try_with` never fails, the `Cell` and `RefCell` helpers lower from std, and a `RefCell` key's `set` and `with_borrow_mut` borrow it as `borrow_mut` does | +| `thread_local!`, `LocalKey` | an `rt.LocalKey`, a module's value that each copy of the key is, which makes its value on first use from the `const` or the expression and never drops it, as wasm32's do; `with` calls its function on that value, `try_with` never fails, the `Cell` and `RefCell` helpers lower from std, and a `RefCell` key's `set` and `with_borrow_mut` borrow it as `borrow_mut` does | | `OnceCell`, `OnceLock` | `rt.OnceCell` and `rt.OnceLock`, holding the `Option` in `v`, which is the place `get_mut` lends where JS copies the value; a `OnceLock` initialized from within its own initialization panics, as wasm32's does, and `get_or_try_init`'s error leaves either empty | | `Once`, `OnceState` | `rt.Once`, an `rt.OnceLock` that holds `true` once `call_once` or `call_once_force` ran its function, and panics as wasm32's does if run again from within it; `rt.OnceState`, that `Once`, which a panic never poisons, as it aborts | | `LazyCell`, `LazyLock` | `rt.LazyCell` and `rt.LazyLock`, which run their function on the first `force` or `Deref`, once, and drop it then, or with the cell if it never ran; forced from within it, either panics as std's does | diff --git a/ts/fixtures/lang/locals/expected/lib.ts b/ts/fixtures/lang/locals/expected/lib.ts index 811fe4e36..3f63204d5 100644 --- a/ts/fixtures/lang/locals/expected/lib.ts +++ b/ts/fixtures/lang/locals/expected/lib.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { locals } from "./locals.ts"; +import { locals, keys } from "./locals.ts"; import type * as Twin from "./locals.Twin.ts"; export function copy$locals_Twin(v: Twin.Twin): Twin.Twin { @@ -19,6 +19,8 @@ export function copy$tuple2_locals_Twin_locals_Twin(v: [Twin.Twin, Twin.Twin]): export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] { return [["locals::locals", (_22: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): undefined => { locals(data, out); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["locals::keys", (_23: rt.u8, data2: rt.ByteSlice, out2: rt.ByteVec): undefined => { + keys(data2, out2); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } diff --git a/ts/fixtures/lang/locals/expected/locals.ts b/ts/fixtures/lang/locals/expected/locals.ts index 80c45ca44..0afb042af 100644 --- a/ts/fixtures/lang/locals/expected/locals.ts +++ b/ts/fixtures/lang/locals/expected/locals.ts @@ -8,7 +8,7 @@ import { mapOr$18ipla0 as result_mapOr$18ipla0 } from "./core/result.ts"; import * as Noisy from "./locals.Noisy.ts"; import type * as Polling from "./locals.Polling.ts"; import type * as Twin from "./locals.Twin.ts"; -import { set$0st49po, get$0x8wyln, get$1ei2eoj, take$09yi5f2, replace$0kxb7te, withBorrow$1qygtee, withBorrow$1k8k9m1, withBorrow$0r4ucls, withBorrow$1t1sbub, withBorrow$15ql9sa, take$1x34chv, take$122f15k, replace$0bb3zeq, replace$1xk72wn } from "./std/thread/local.ts"; +import { set$0st49po, set$0xuu1fw, get$0x8wyln, get$1ei2eoj, get$18ignl5, take$09yi5f2, replace$0kxb7te, withBorrow$1qygtee, withBorrow$1k8k9m1, withBorrow$0r4ucls, withBorrow$1t1sbub, withBorrow$15ql9sa, take$1x34chv, take$122f15k, replace$0bb3zeq, replace$1xk72wn } from "./std/thread/local.ts"; export const COUNT: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey(COUNT$init, false); @@ -22,6 +22,12 @@ export function HELD$init(): rt.RefCell { return new rt.RefCell(undefined as Noisy.Noisy | undefined); } +export const LEFT: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey(LEFT$init, false); + +export function LEFT$init(): rt.Cell { + return new rt.Cell(1); +} + export const LOG: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey((): rt.RefCell => { return LOG$init(undefined); }, true); @@ -45,6 +51,14 @@ export function POLLING$init(): Polling.Polling { return { current: new rt.Cell(undefined as [rt.u64, rt.u64] | undefined), held: new rt.RefCell(new rt.ByteVec()) }; } +export const RIGHT: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey((): rt.Cell => { + return RIGHT$init(undefined); +}, true); + +export function RIGHT$init(_1: undefined): rt.Cell { + return new rt.Cell(2); +} + export const TALLY: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey((): rt.RefCell => { return TALLY$init(undefined); }, true); @@ -261,4 +275,42 @@ export function NEXT$init$serial(): rt.Cell<[rt.u8, rt.u8]> { return new rt.Cell([0, 4] as [rt.u8, rt.u8]); } +/** `*r = v`, through the reference. */ +export function assign$thread_LocalKey_Cell_u8(r: rt.Place>>, v: rt.LocalKey>): void { + r.v = v; +} + +/** + * Each byte a copy of `LEFT` written whole with `RIGHT` by each way std moves a value, or + * not at all, after which adding the byte through the copy changes only the key it names. + * Each puts what the replaced copy names, then what each key holds. A key is used only + * through a `&'static`, so each copy is leaked. + */ +export function keys(data: rt.ByteSlice, out: rt.ByteVec): void { + for (const op of rt.byteSliceView(data)) { + const key: rt.Place>> = { v: LEFT }; + const _12: rt.u8 = rt.u8Rem(op, 5); + if (_12 === 0) { + assign$thread_LocalKey_Cell_u8(key, RIGHT); + } else if (_12 === 1) { + const old = key.v; + key.v = RIGHT; + rt.byteVecPush(out, get$18ignl5({ v: old }.v)); + } else if (_12 === 2) { + const other: rt.Place>> = { v: RIGHT }; + const swapped = key.v; + key.v = other.v; + other.v = swapped; + rt.byteVecPush(out, get$18ignl5(other.v)); + } else if (_12 === 3) { + rt.cellSet(rt.cellOfPlace(key), RIGHT); + } + const _28: rt.u8 = rt.u8WrappingAdd(get$18ignl5(key.v), op); + set$0xuu1fw(key.v, _28); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(get$18ignl5(LEFT), get$18ignl5(RIGHT))), undefined, true); + } + set$0xuu1fw(LEFT, 1); + set$0xuu1fw(RIGHT, 2); +} + //# sourceMappingURL=locals.ts.map diff --git a/ts/fixtures/lang/locals/expected/std/thread/local.ts b/ts/fixtures/lang/locals/expected/std/thread/local.ts index 26c85b860..4fbe683cb 100644 --- a/ts/fixtures/lang/locals/expected/std/thread/local.ts +++ b/ts/fixtures/lang/locals/expected/std/thread/local.ts @@ -13,6 +13,14 @@ export function set$0st49po(self: rt.LocalKey>, value: rt.u32): }); } +export function set$0xuu1fw(self: rt.LocalKey>, value: rt.u8): void { + rt.localKeyInitializeWith(self, new rt.Cell(value), (value2: rt.Cell | undefined, cell: rt.Cell): undefined => { + if (value2 !== undefined) { + rt.cellSet(cell, value2.v); + } + }); +} + export function get$0x8wyln(self: rt.LocalKey>): [Twin.Twin, Twin.Twin] { return ((a0: rt.Cell<[Twin.Twin, Twin.Twin]>): [Twin.Twin, Twin.Twin] => { return copy$tuple2_locals_Twin_locals_Twin(a0.v); @@ -25,6 +33,12 @@ export function get$1ei2eoj(self: rt.LocalKey>): rt.u32 { })(self.get()); } +export function get$18ignl5(self: rt.LocalKey>): rt.u8 { + return ((a0: rt.Cell): rt.u8 => { + return a0.v; + })(self.get()); +} + export function take$09yi5f2(self: rt.LocalKey>): rt.u32 { return ((a0: rt.Cell): rt.u32 => { return rt.cellReplace(a0, 0); diff --git a/ts/fixtures/lang/locals/src/lib.rs b/ts/fixtures/lang/locals/src/lib.rs index 91d91dd71..b8ce24820 100644 --- a/ts/fixtures/lang/locals/src/lib.rs +++ b/ts/fixtures/lang/locals/src/lib.rs @@ -15,5 +15,8 @@ use harness::Case; /// Each case's name and what it runs. The first two bytes, big-endian, pick one modulo /// their count, so a case added at the end moves no other. fn cases() -> Vec<(&'static str, Case)> { - vec![("locals::locals", |_, data, out| locals::locals(data, out))] + vec![ + ("locals::locals", |_, data, out| locals::locals(data, out)), + ("locals::keys", |_, data, out| locals::keys(data, out)), + ] } diff --git a/ts/fixtures/lang/locals/src/locals.rs b/ts/fixtures/lang/locals/src/locals.rs index 26047aa16..2f810bb1b 100644 --- a/ts/fixtures/lang/locals/src/locals.rs +++ b/ts/fixtures/lang/locals/src/locals.rs @@ -2,12 +2,15 @@ //! and a struct of both, as kio's `Polling` is, initialized by a `const` block or lazily //! by an expression; read and written through `with`, given a closure or a function, //! `try_with`, and the `Cell` and `RefCell` helpers, as kio's `coop` does; nested; and -//! declared in a function, as kio's `serial` is. Each case leaves +//! declared in a function, as kio's `serial` is; and a copy of a key written whole through +//! a `&mut`, which then names the other's storage. Each case leaves //! every thread-local as it found it, so a fresh instance and a reused one agree. use std::cell::{Cell, RefCell}; use std::marker::PhantomData; +use std::mem; use std::sync::{Arc, Mutex}; +use std::thread::LocalKey; /// Logs its id when it drops. struct Noisy { @@ -49,6 +52,9 @@ thread_local! { x.0 = 3; Cell::new((x, y)) }; + /// The keys `keys` writes over each other's copies, initialized by a `const` block or lazily. + static LEFT: Cell = const { Cell::new(1) }; + static RIGHT: Cell = Cell::new(2); } /// Each byte an operation on the thread-locals; each puts what it read and what dropped. @@ -158,3 +164,33 @@ fn serial(reset: bool) -> u8 { n }) } + +/// `*r = v`, through the reference. +fn assign(r: &mut T, v: T) { + *r = v; +} + +/// Each byte a copy of `LEFT` written whole with `RIGHT` by each way std moves a value, or +/// not at all, after which adding the byte through the copy changes only the key it names. +/// Each puts what the replaced copy names, then what each key holds. A key is used only +/// through a `&'static`, so each copy is leaked. +pub fn keys(data: &[u8], out: &mut Vec) { + for &op in data { + let key: &'static mut LocalKey> = Box::leak(Box::new(LEFT)); + match op % 5 { + 0 => assign(key, RIGHT), + 1 => out.push(Box::leak(Box::new(mem::replace(key, RIGHT))).get()), + 2 => { + let other = Box::leak(Box::new(RIGHT)); + mem::swap(key, other); + out.push(other.get()); + } + 3 => Cell::from_mut(&mut *key).set(RIGHT), + _ => {} + } + key.set(key.get().wrapping_add(op)); + out.extend([LEFT.get(), RIGHT.get()]); + } + LEFT.set(1); + RIGHT.set(2); +} diff --git a/ts/fixtures/lang/statics/caller.ts b/ts/fixtures/lang/statics/caller.ts index d21a45209..874d4c30d 100644 --- a/ts/fixtures/lang/statics/caller.ts +++ b/ts/fixtures/lang/statics/caller.ts @@ -14,4 +14,7 @@ const onces = [[1, 0, 1, 7], [0, 6, 1, 2, 3, 4, 5, 11], [2, 14, 8], [9], [3, 15] /** `statics::lazies` scripts: each lazy cell and lock printed before it is forced, after, and from within its own function. */ const lazies = [[0, 1, 2, 3, 4, 5, 4, 5, 0]].map((bytes) => Uint8Array.of(...op("statics::lazies"), 0, ...bytes)); -export const cases: Uint8Array[] = [...onces, ...lazies]; +/** `statics::writes` scripts: each way of writing a lazy cell and lock, with neither, either, or both forced first. */ +const writes = [Array.from({ length: 56 }, (_, i) => i)].map((bytes) => Uint8Array.of(...op("statics::writes"), 0, ...bytes)); + +export const cases: Uint8Array[] = [...onces, ...lazies, ...writes]; diff --git a/ts/fixtures/lang/statics/expected/lib.ts b/ts/fixtures/lang/statics/expected/lib.ts index 38bbf19e8..ecdb7ce2a 100644 --- a/ts/fixtures/lang/statics/expected/lib.ts +++ b/ts/fixtures/lang/statics/expected/lib.ts @@ -1,9 +1,14 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { statics, cells, onces, lazies } from "./statics.ts"; +import { statics, cells, onces, lazies, writes } from "./statics.ts"; +import type * as Dropped from "./statics.Dropped.ts"; import * as Lock from "./statics.Lock.ts"; +export function drop$cell_LazyCell_statics_Dropped_statics_cell_closure0(value: rt.LazyCell): void { + rt.lazyDrop(value, rt.drop); +} + export function drop$statics_Lock_Rc_unit(_1: Lock.Lock>): void { Lock.drop$ops_Drop_Rc_unit(_1); } @@ -12,6 +17,10 @@ export function drop$statics_Lock_tuple2_u8_Rc_unit(_1: Lock.Lock<[rt.u8, rt.Arc Lock.drop$ops_Drop_tuple2_u8_Rc_unit(_1); } +export function drop$sync_LazyLock_statics_Dropped_statics_lock_closure0(value: rt.LazyLock): void { + rt.lazyDrop(value, rt.drop); +} + export function drop$tuple2_u8_Rc_unit(_1: [rt.u8, rt.Arc]): void { _1[1][Symbol.dispose](); } @@ -29,6 +38,8 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) onces(data3, out3); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["statics::lazies", (_25: rt.u8, data4: rt.ByteSlice, out4: rt.ByteVec): undefined => { lazies(data4, out4); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["statics::writes", (_26: rt.u8, data5: rt.ByteSlice, out5: rt.ByteVec): undefined => { + writes(data5, out5); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } diff --git a/ts/fixtures/lang/statics/expected/statics.Dropped.ts b/ts/fixtures/lang/statics/expected/statics.Dropped.ts new file mode 100644 index 000000000..4da0ef4f1 --- /dev/null +++ b/ts/fixtures/lang/statics/expected/statics.Dropped.ts @@ -0,0 +1,31 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { DROPS } from "./statics.ts"; + +export function fmt$fmt_Debug(self: Dropped, f: rt.Formatter): rt.Result { + const _3: string = "Dropped"; + const _4: rt.Fmt = (f: rt.Formatter) => rt.u8Debug(self._0, f); + return rt.debugTupleFinish(rt.debugTupleField(rt.fmtDebugTuple(f, _3), _4)); +} + +/** Logs its id when it drops. */ +export class Dropped { + _0: rt.u8; + + constructor(_0: rt.u8) { + this._0 = _0; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function drop$ops_Drop(self: Dropped): void { + rt.localKeyWithBorrowMut(DROPS, (drops: rt.ByteVec): undefined => { + rt.byteVecPush(drops, self._0); + }); +} + +//# sourceMappingURL=statics.Dropped.ts.map diff --git a/ts/fixtures/lang/statics/expected/statics.ts b/ts/fixtures/lang/statics/expected/statics.ts index 9c5460c53..018f546e6 100644 --- a/ts/fixtures/lang/statics/expected/statics.ts +++ b/ts/fixtures/lang/statics/expected/statics.ts @@ -1,14 +1,15 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { drop$statics_Lock_Rc_unit, drop$statics_Lock_tuple2_u8_Rc_unit } from "./lib.ts"; +import { drop$cell_LazyCell_statics_Dropped_statics_cell_closure0, drop$statics_Lock_Rc_unit, drop$statics_Lock_tuple2_u8_Rc_unit, drop$sync_LazyLock_statics_Dropped_statics_lock_closure0 } from "./lib.ts"; import { LATER, read } from "./statics/elsewhere.ts"; import * as Collector from "./statics.Collector.ts"; import type * as Counter from "./statics.Counter.ts"; +import * as Dropped from "./statics.Dropped.ts"; import * as Lock from "./statics.Lock.ts"; import type * as Node from "./statics.Node.ts"; import type * as Raw from "./statics.Raw.ts"; -import { withBorrow$1roy6g4, take$1357qwq } from "./std/thread/local.ts"; +import { withBorrow$1roy6g4, take$1357qwq, take$122f15k } from "./std/thread/local.ts"; export const COUNT: rt.Atomic = /* @__PURE__ */ new rt.Atomic({ hi: 0xffffffff, lo: 0xfffffffd } as rt.u64); @@ -431,4 +432,205 @@ export function lazies(data: rt.ByteSlice, out: rt.ByteVec): void { } } +export const DROPS: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey(DROPS$init, false); + +export function DROPS$init(): rt.RefCell { + return new rt.RefCell(new rt.ByteVec()); +} + +export function cell(id: rt.u8): rt.LazyCell { + const held: Dropped.Dropped = new Dropped.Dropped(id); + const captured = held._0; + const _0: rt.LazyCell = new rt.LazyCell((): Dropped.Dropped => { + return new Dropped.Dropped(rt.u8Add(rt.u8Add(captured, 10), rt.atomicFetch(FORCED, rt.u8WrappingAdd, 1, 0))); + }); + held[Symbol.dispose](); + return _0; +} + +export function lock(id: rt.u8): rt.LazyLock { + const held: Dropped.Dropped = new Dropped.Dropped(id); + const captured = held._0; + const _0: rt.LazyLock = new rt.LazyLock((): Dropped.Dropped => { + return new Dropped.Dropped(rt.u8Add(rt.u8Add(captured, 10), rt.atomicFetch(FORCED, rt.u8WrappingAdd, 1, 0))); + }); + held[Symbol.dispose](); + return _0; +} + +/** `*r = v`, through the reference. */ +export function assign$boxed_Box_cell_LazyCell_statics_Dropped_statics_cell_closure0(r: rt.Place>, v: rt.LazyCell): void { + rt.lazyDrop(r.v, rt.drop); + r.v = v; +} + +/** `*r = v`, through the reference. */ +export function assign$boxed_Box_sync_LazyLock_statics_Dropped_statics_lock_closure0(r: rt.Place>, v: rt.LazyLock): void { + rt.lazyDrop(r.v, rt.drop); + r.v = v; +} + +/** `*r = v`, through the reference. */ +export function assign$cell_LazyCell_statics_Dropped_statics_cell_closure0(r: rt.LazyCell, v: rt.LazyCell): void { + rt.lazyDrop(r, rt.drop); + rt.ptrWrite(r, v); +} + +/** `*r = v`, through the reference. */ +export function assign$sync_LazyLock_statics_Dropped_statics_lock_closure0(r: rt.LazyLock, v: rt.LazyLock): void { + rt.lazyDrop(r, rt.drop); + rt.ptrWrite(r, v); +} + +/** + * Each byte a lazy cell or lock written whole through a `&mut` by each way std moves a + * value, after each, the one written and the one given, is forced or not; then it is + * printed, forced, and dropped, and each `Dropped` dropped meanwhile put in order. + */ +export function writes(data: rt.ByteSlice, out: rt.ByteVec): void { + for (const op of rt.byteSliceView(data)) { + if (rt.u8Rem(op, 2) === 0) { + written$cell_LazyCell_statics_Dropped_statics_cell_closure0(rt.u8Div(op, 2), cell, out); + } else { + written$sync_LazyLock_statics_Dropped_statics_lock_closure0(rt.u8Div(op, 2), lock, out); + } + rt.iterExtendBytes(out, rt.byteVecIntoIter(take$122f15k(DROPS)), undefined, true); + rt.byteVecPush(out, rt.atomicSwap(FORCED, 0, 0)); + } +} + +export function written$cell_LazyCell_statics_Dropped_statics_cell_closure0(op: rt.u8, make: (a0: rt.u8) => rt.LazyCell, out: rt.ByteVec): void { + const _18: (a0: rt.LazyCell) => string = (l: rt.LazyCell): string => { + const args2: rt.Fmt[] = [(f: rt.Formatter) => l.debug(f, "LazyCell", (value: Dropped.Dropped, f: rt.Formatter) => Dropped.fmt$fmt_Debug(value, f))]; + return rt.fmtFormat([args2[0], "|"]); + }; + let _103: boolean = false; + let a: rt.LazyCell = make(1); + let b: rt.LazyCell = make(2); + _103 = true; + if ((op & 1) !== 0) { + rt.byteVecPush(out, a.force()._0); + } + if ((op & 2) !== 0) { + rt.byteVecPush(out, b.force()._0); + } + const _20: rt.u8 = rt.u8Rem(op >> 2, 7); + if (_20 === 0) { + const _25: rt.LazyCell = a; + _103 = false; + assign$cell_LazyCell_statics_Dropped_statics_cell_closure0(_25, b); + } else if (_20 === 1) { + const _28: rt.LazyCell = a; + _103 = false; + const old: rt.LazyCell = rt.replaceIn(_28, b); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(old))); + rt.lazyDrop(old, rt.drop); + } else if (_20 === 2) { + b = rt.replaceIn(a, b); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(b))); + rt.byteVecPush(out, b.force()._0); + } else if (_20 === 3) { + const _52: rt.Cell> = rt.cellIn(a); + _103 = false; + const old2: rt.LazyCell = rt.cellReplace(_52, b); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(old2))); + rt.lazyDrop(old2, rt.drop); + } else if (_20 === 4) { + const _62: rt.Cell> = rt.cellIn(a); + _103 = false; + rt.cellSet(_62, b, drop$cell_LazyCell_statics_Dropped_statics_cell_closure0); + } else if (_20 === 5) { + let boxed: rt.LazyCell = a; + _103 = false; + const _69: rt.LazyCell = b; + assign$boxed_Box_cell_LazyCell_statics_Dropped_statics_cell_closure0(rt.place(() => boxed, (value) => boxed = value), _69); + a = boxed; + } else { + const held: rt.RefCell<[rt.u8, rt.LazyCell]> = new rt.RefCell([0, a] as [rt.u8, rt.LazyCell]); + const _79: rt.RefCell<[rt.u8, rt.LazyCell]> = rt.refCellBorrowMut(held); + const _78: rt.RefCell> = rt.refMutMap(_79, rt.partPlace(((p: [rt.u8, rt.LazyCell]): rt.LazyCell => { + return p[1]; + })(_79.value))); + _103 = false; + assign$cell_LazyCell_statics_Dropped_statics_cell_closure0(_78.value, b); + rt.refMutRelease(_78); + a = held.value[1]; + } + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(a))); + rt.byteVecPush(out, a.force()._0); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(a))); + if (_103) { + rt.lazyDrop(b, rt.drop); + } + _103 = false; + rt.lazyDrop(a, rt.drop); +} + +export function written$sync_LazyLock_statics_Dropped_statics_lock_closure0(op: rt.u8, make: (a0: rt.u8) => rt.LazyLock, out: rt.ByteVec): void { + const _18: (a0: rt.LazyLock) => string = (l: rt.LazyLock): string => { + const args2: rt.Fmt[] = [(f: rt.Formatter) => l.debug(f, "LazyLock", (value: Dropped.Dropped, f: rt.Formatter) => Dropped.fmt$fmt_Debug(value, f))]; + return rt.fmtFormat([args2[0], "|"]); + }; + let _103: boolean = false; + let a: rt.LazyLock = make(1); + let b: rt.LazyLock = make(2); + _103 = true; + if ((op & 1) !== 0) { + rt.byteVecPush(out, a.force()._0); + } + if ((op & 2) !== 0) { + rt.byteVecPush(out, b.force()._0); + } + const _20: rt.u8 = rt.u8Rem(op >> 2, 7); + if (_20 === 0) { + const _25: rt.LazyLock = a; + _103 = false; + assign$sync_LazyLock_statics_Dropped_statics_lock_closure0(_25, b); + } else if (_20 === 1) { + const _28: rt.LazyLock = a; + _103 = false; + const old: rt.LazyLock = rt.replaceIn(_28, b); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(old))); + rt.lazyDrop(old, rt.drop); + } else if (_20 === 2) { + b = rt.replaceIn(a, b); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(b))); + rt.byteVecPush(out, b.force()._0); + } else if (_20 === 3) { + const _52: rt.Cell> = rt.cellIn(a); + _103 = false; + const old2: rt.LazyLock = rt.cellReplace(_52, b); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(old2))); + rt.lazyDrop(old2, rt.drop); + } else if (_20 === 4) { + const _62: rt.Cell> = rt.cellIn(a); + _103 = false; + rt.cellSet(_62, b, drop$sync_LazyLock_statics_Dropped_statics_lock_closure0); + } else if (_20 === 5) { + let boxed: rt.LazyLock = a; + _103 = false; + const _69: rt.LazyLock = b; + assign$boxed_Box_sync_LazyLock_statics_Dropped_statics_lock_closure0(rt.place(() => boxed, (value) => boxed = value), _69); + a = boxed; + } else { + const held: rt.RefCell<[rt.u8, rt.LazyLock]> = new rt.RefCell([0, a] as [rt.u8, rt.LazyLock]); + const _79: rt.RefCell<[rt.u8, rt.LazyLock]> = rt.refCellBorrowMut(held); + const _78: rt.RefCell> = rt.refMutMap(_79, rt.partPlace(((p: [rt.u8, rt.LazyLock]): rt.LazyLock => { + return p[1]; + })(_79.value))); + _103 = false; + assign$sync_LazyLock_statics_Dropped_statics_lock_closure0(_78.value, b); + rt.refMutRelease(_78); + a = held.value[1]; + } + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(a))); + rt.byteVecPush(out, a.force()._0); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_18(a))); + if (_103) { + rt.lazyDrop(b, rt.drop); + } + _103 = false; + rt.lazyDrop(a, rt.drop); +} + //# sourceMappingURL=statics.ts.map diff --git a/ts/fixtures/lang/statics/expected/std/thread/local.ts b/ts/fixtures/lang/statics/expected/std/thread/local.ts index adee050b4..a53ad3333 100644 --- a/ts/fixtures/lang/statics/expected/std/thread/local.ts +++ b/ts/fixtures/lang/statics/expected/std/thread/local.ts @@ -19,4 +19,10 @@ export function take$1357qwq(self: rt.LocalKey>): rt.ByteVec { + return ((a0: rt.RefCell): rt.ByteVec => { + return rt.refCellReplace(a0, new rt.ByteVec()); + })(self.get()); +} + //# sourceMappingURL=local.ts.map diff --git a/ts/fixtures/lang/statics/src/lib.rs b/ts/fixtures/lang/statics/src/lib.rs index de12a54fb..d26fbf988 100644 --- a/ts/fixtures/lang/statics/src/lib.rs +++ b/ts/fixtures/lang/statics/src/lib.rs @@ -20,5 +20,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("statics::cells", |_, data, out| statics::cells(data, out)), ("statics::onces", |_, data, out| statics::onces(data, out)), ("statics::lazies", |_, data, out| statics::lazies(data, out)), + ("statics::writes", |_, data, out| statics::writes(data, out)), ] } diff --git a/ts/fixtures/lang/statics/src/statics.rs b/ts/fixtures/lang/statics/src/statics.rs index b259e2b24..54f501e45 100644 --- a/ts/fixtures/lang/statics/src/statics.rs +++ b/ts/fixtures/lang/statics/src/statics.rs @@ -5,12 +5,16 @@ //! `LazyLock`, and `Once`, crossbeam-epoch's own `OnceLock` over an `UnsafeCell`, and one //! pointing at a static of a module that reads it back; and `const`s holding cells, each //! use of which is a value of its own, as lock_api's `RawMutex::INIT` is, and each lazy one -//! printed before and after it is forced, and from within its own function. Each case leaves +//! printed before and after it is forced, and from within its own function; and lazy cells +//! and locks written whole through a `&mut`, forced or not, each way std moves a value. +//! Each case leaves //! every static as it found it, or reads only what it holds whatever ran before, so a fresh //! instance and a reused one agree. -use std::cell::{Cell, LazyCell, RefCell, UnsafeCell}; -use std::mem::MaybeUninit; +use std::cell::{Cell, LazyCell, RefCell, RefMut, UnsafeCell}; +use std::fmt::Debug; +use std::mem::{self, MaybeUninit}; +use std::ops::Deref; use std::ptr; use std::rc::Rc; use std::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, SeqCst}; @@ -467,3 +471,95 @@ pub fn lazies(data: &[u8], out: &mut Vec) { out.extend_from_slice(text.as_bytes()); } } + +thread_local! { + /// The ids of the `Dropped`s dropped, in order. + static DROPS: RefCell> = const { RefCell::new(Vec::new()) }; +} + +/// Logs its id when it drops. +#[derive(Debug)] +struct Dropped(u8); + +impl Drop for Dropped { + fn drop(&mut self) { + DROPS.with_borrow_mut(|drops| drops.push(self.0)); + } +} + +/// A lazy cell or lock whose function owns a `Dropped` of `id`, which drops once it has +/// run, or with the cell if it never does, and makes one of `id + 10` and the count forced. +macro_rules! lazy { + ($name:ident, $ty:ident) => { + fn $name(id: u8) -> $ty Dropped> { + let held = Dropped(id); + $ty::new(move || Dropped(held.0 + 10 + FORCED.fetch_add(1, Relaxed))) + } + }; +} + +lazy!(cell, LazyCell); +lazy!(lock, LazyLock); + +/// `*r = v`, through the reference. +fn assign(r: &mut T, v: T) { + *r = v; +} + +/// Each byte a lazy cell or lock written whole through a `&mut` by each way std moves a +/// value, after each, the one written and the one given, is forced or not; then it is +/// printed, forced, and dropped, and each `Dropped` dropped meanwhile put in order. +pub fn writes(data: &[u8], out: &mut Vec) { + for &op in data { + match op % 2 { + 0 => written(op / 2, cell, out), + _ => written(op / 2, lock, out), + } + out.extend(DROPS.take()); + out.push(FORCED.swap(0, Relaxed)); + } +} + +fn written + Debug>(op: u8, make: fn(u8) -> L, out: &mut Vec) { + let mut a = make(1); + let mut b = make(2); + if op & 1 != 0 { + out.push(a.0); + } + if op & 2 != 0 { + out.push(b.0); + } + let text = |l: &L| format!("{l:?}|"); + match (op >> 2) % 7 { + 0 => assign(&mut a, b), + 1 => { + let old = mem::replace(&mut a, b); + out.extend_from_slice(text(&old).as_bytes()); + } + 2 => { + mem::swap(&mut a, &mut b); + out.extend_from_slice(text(&b).as_bytes()); + out.push(b.0); + } + 3 => { + let old = Cell::from_mut(&mut a).replace(b); + out.extend_from_slice(text(&old).as_bytes()); + } + 4 => Cell::from_mut(&mut a).set(b), + // A box is what it holds. + 5 => { + let mut boxed = Box::new(a); + assign(&mut boxed, Box::new(b)); + a = *boxed; + } + // Through a guard of the part. + _ => { + let held = RefCell::new((0u8, a)); + assign(&mut *RefMut::map(held.borrow_mut(), |p| &mut p.1), b); + a = held.into_inner().1; + } + } + out.extend_from_slice(text(&a).as_bytes()); + out.push(a.0); + out.extend_from_slice(text(&a).as_bytes()); +} diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 2d75a2da7..ecfe6f12b 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -128,7 +128,6 @@ fixtures/reject/src/places.rs:rebound_reference+7:14: a copy of a closure that c fixtures/reject/src/places.rs:rebound_reference+8:14: a copy of a closure that changes what it captured fixtures/reject/src/places.rs:replace_boxed_bytes+4:2: replacing the value behind a reference to `std::pin::Pin>` fixtures/reject/src/places.rs:replace_boxed_future+4:2: an assignment through a reference to `std::pin::Pin>>` -fixtures/reject/src/places.rs:replace_boxed_poll_fn+4:2: replacing the value behind a reference to `std::pin::Pin fn(&'a mut std::task::Context<'b>) -> std::task::Poll>>>` fixtures/reject/src/places.rs:replace_guard+1:2: an assignment through a reference to `std::sync::MutexGuard<'_, u8>` fixtures/reject/src/places.rs:replace_pinned_ref+1:2: an assignment through a reference to `std::pin::Pin<&mut places::Noisy>` fixtures/reject/src/places.rs:swap_guards+1:2: replacing the value behind a reference to `std::sync::MutexGuard<'_, u8>` @@ -218,9 +217,6 @@ fixtures/reject/src/rebound.rs:retargeted+5:10: a raw pointer to `rebound::Targe fixtures/reject/src/rebound.rs:swap_retargeted+6:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there fixtures/reject/src/rebound.rs:swapped_in_loop+8:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there fixtures/reject/src/rebound.rs:unboxed_rebound+2:16: a reference to `rebound::Unboxed`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there -fixtures/reject/src/statics.rs:key_celled+1:2: a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which JS can't write in place -fixtures/reject/src/statics.rs:lazy_assigned+1:2: an assignment through a reference to `std::sync::LazyLock` -fixtures/reject/src/statics.rs:lazy_celled+1:3: a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which JS can't write in place fixtures/reject/src/statics.rs:pointer_statics+3:15: comparing pointers to `u32` in an atomic, which JS has no address of fixtures/reject/src/statics.rs:pointer_statics+4:3: a constant of type `&u32`, a pointer into memory translation can't name fixtures/reject/src/statics.rs:statics+1:11: a mutable static, which any code may write diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index a5e11e31d..325d000e7 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -129,7 +129,6 @@ fixtures/reject/src/places.rs:rebound_reference+7:14: a copy of a closure that c fixtures/reject/src/places.rs:rebound_reference+8:14: a copy of a closure that changes what it captured fixtures/reject/src/places.rs:replace_boxed_bytes+4:2: replacing the value behind a reference to `std::pin::Pin>` fixtures/reject/src/places.rs:replace_boxed_future+4:2: an assignment through a reference to `std::pin::Pin>>` -fixtures/reject/src/places.rs:replace_boxed_poll_fn+4:2: replacing the value behind a reference to `std::pin::Pin fn(&'a mut std::task::Context<'b>) -> std::task::Poll>>>` fixtures/reject/src/places.rs:replace_guard+1:2: an assignment through a reference to `std::sync::MutexGuard<'_, u8>` fixtures/reject/src/places.rs:replace_pinned_ref+1:2: an assignment through a reference to `std::pin::Pin<&mut places::Noisy>` fixtures/reject/src/places.rs:swap_guards+1:2: replacing the value behind a reference to `std::sync::MutexGuard<'_, u8>` @@ -218,9 +217,6 @@ fixtures/reject/src/rebound.rs:retargeted+5:10: a raw pointer to `rebound::Targe fixtures/reject/src/rebound.rs:swap_retargeted+6:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there fixtures/reject/src/rebound.rs:swapped_in_loop+8:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there fixtures/reject/src/rebound.rs:unboxed_rebound+2:16: a reference to `rebound::Unboxed`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there -fixtures/reject/src/statics.rs:key_celled+1:2: a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which JS can't write in place -fixtures/reject/src/statics.rs:lazy_assigned+1:2: an assignment through a reference to `std::sync::LazyLock` -fixtures/reject/src/statics.rs:lazy_celled+1:3: a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which JS can't write in place fixtures/reject/src/statics.rs:pointer_statics+3:15: comparing pointers to `u32` in an atomic, which JS has no address of fixtures/reject/src/statics.rs:pointer_statics+4:3: a constant of type `&u32`, a pointer into memory translation can't name fixtures/reject/src/statics.rs:statics+1:11: a mutable static, which any code may write diff --git a/ts/fixtures/reject/src/places.rs b/ts/fixtures/reject/src/places.rs index 2bf5e4533..5dc9e8959 100644 --- a/ts/fixtures/reject/src/places.rs +++ b/ts/fixtures/reject/src/places.rs @@ -11,14 +11,6 @@ pub fn replace_guard<'a>(dst: &mut std::sync::MutexGuard<'a, u8>, src: std::sync *dst = src; } -/// A boxed `PollFn` is the `rt.PollFn`, whose callback is private where a copy can't reach. -pub fn replace_boxed_poll_fn( - dst: &mut std::pin::Pin) -> std::task::Poll>>>, - src: std::pin::Pin) -> std::task::Poll>>>, -) -> std::pin::Pin) -> std::task::Poll>>> { - std::mem::replace(dst, src) -} - /// A boxed byte array is a `Uint8Array`, which a replacement can't copy out as an object. pub fn replace_boxed_bytes( dst: &mut std::pin::Pin>, diff --git a/ts/fixtures/reject/src/statics.rs b/ts/fixtures/reject/src/statics.rs index bd6e9611d..e130c2327 100644 --- a/ts/fixtures/reject/src/statics.rs +++ b/ts/fixtures/reject/src/statics.rs @@ -1,4 +1,4 @@ -//! Statics and lazy cells whose state JS can't share or copy. +//! Statics whose state JS can't share or copy. static mut COUNT: u8 = 0; @@ -17,24 +17,3 @@ pub fn pointer_statics(p: *mut u32) -> u32 { let _ = SLOT.compare_exchange(p, std::ptr::null_mut(), Relaxed, Relaxed); *NAMED } - -const LAZY_CELL: std::cell::LazyCell = std::cell::LazyCell::new(|| 2); - -const LAZY_LOCK: std::sync::LazyLock = std::sync::LazyLock::new(|| 2); - -/// A lazy cell's state, which JS keeps private, so copying its fields can't move it. -pub fn lazy_celled(c: &mut std::cell::LazyCell) -> u32 { - *std::cell::Cell::from_mut(c).replace(LAZY_CELL) + **c -} - -/// A lazy lock written whole through a reference, which can't copy its private state. -pub fn lazy_assigned(c: &mut std::sync::LazyLock) -> u32 { - *c = LAZY_LOCK; - **c -} - -/// A thread local's key, whose state JS keeps private, set whole through a cell. -pub fn key_celled(key: &'static mut std::thread::LocalKey, next: std::thread::LocalKey) -> u32 { - std::cell::Cell::from_mut(key).set(next); - key.with(|v| *v) -} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index ceda108fe..6fa338c5c 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -107,7 +107,6 @@ roots = [ "places::rebound_reference", "places::replace_boxed_bytes", "places::replace_boxed_future", - "places::replace_boxed_poll_fn", "places::replace_guard", "places::replace_pinned_ref", "places::swap_guards", @@ -165,9 +164,6 @@ roots = [ "rebound::swap_retargeted", "rebound::swapped_in_loop", "rebound::unboxed_rebound", - "statics::key_celled", - "statics::lazy_assigned", - "statics::lazy_celled", "statics::pointer_statics", "statics::statics", "strs::cow_borrowed", diff --git a/ts/fixtures/task/expected/futures.ts b/ts/fixtures/task/expected/futures.ts index 246a5afa6..e61fd0ae9 100644 --- a/ts/fixtures/task/expected/futures.ts +++ b/ts/fixtures/task/expected/futures.ts @@ -340,6 +340,18 @@ export class wakersFuture { } } +/** Write `value` through `slot`, which a dangling pointer to `()` may give. */ +export function put$Pin_boxed_Box_future_PollFn_futures_owning_closure0(slot: rt.Future, value: rt.Future): void { + slot[Symbol.dispose](); + rt.ptrWrite(slot, value); +} + +/** Write `value` through `slot`, which a dangling pointer to `()` may give. */ +export function put$future_PollFn_futures_owning_closure0(slot: rt.Future, value: rt.Future): void { + slot[Symbol.dispose](); + rt.ptrWrite(slot, value); +} + /** Write `value` through `slot`, which a dangling pointer to `()` may give. */ export function put$unit(slot: rt.Place, _: undefined): void { slot.v = undefined; @@ -348,7 +360,8 @@ export function put$unit(slot: rt.Place, _: undefined): void { /** * Boxed futures over a channel: `Box::pin` of an async fn, awaited, and of a `&mut` to a * `poll_fn` and to a `&mut` to it, whose box drops without dropping it, and a `&mut dyn - * Future` of a `&mut` to it, then a queue of + * Future` of a `&mut` to it; `poll_fn`s written whole through a `&mut`, boxed or not, by + * each way std moves one, each written one's guard dropping first; then a queue of * `Pin>`s of an async block, kio's `Pending`, a struct of the crate's, * and a `poll_fn`, each owning a guard. A `poll_fn` boxed in a [`Forward`] polls the * queue in turn through [`poll_unsized`], as moq's tasks do through kio's @@ -373,10 +386,18 @@ export class boxedFuture { #saved!: rt.u64; #awaitee$3!: rt.Future; #awaitee$4!: rt.Future; + #other!: rt.Future; + #old!: rt.Future; + #boxed!: rt.Future; + #moved!: rt.u64; + #awaitee$5!: rt.Future; #pinned!: Pending.Pending<(a0: Waiter.Waiter) => rt.Poll>; - #awaitee$5!: Forward.Forward; + #awaitee$6!: Forward.Forward; #saved$2!: boolean; #saved$3!: boolean; + #saved$4!: boolean; + #saved$5!: boolean; + #saved$6!: boolean; constructor(rx: Consumer.Consumer, guard: Guard.Guard) { this.#rx = rx; @@ -385,38 +406,65 @@ export class boxedFuture { [Symbol.dispose](): void { b0: { - if (this.#state === 0) { - this.#rx[Symbol.dispose](); - this.#guard[Symbol.dispose](); - return; - } - if (this.#state === 3) { - rt.drop(this.#awaitee); - break b0; - } - if (this.#state === 4) { - rt.drop(this.#awaitee$2); - } else if (this.#state === 5) { - rt.drop(this.#awaitee$3); - } else if (!(this.#state === 6 || this.#state === 7)) { + b1: { + if (this.#state === 0) { + this.#rx[Symbol.dispose](); + this.#guard[Symbol.dispose](); + return; + } + if (this.#state === 3) { + rt.drop(this.#awaitee); + break b0; + } + if (this.#state === 4) { + rt.drop(this.#awaitee$2); + break b1; + } + if (this.#state === 5) { + rt.drop(this.#awaitee$3); + break b1; + } + if (this.#state === 6 || this.#state === 7) { + break b1; + } if (this.#state === 8) { this.#awaitee$5[Symbol.dispose](); + } else if (this.#state === 9) { + this.#old[Symbol.dispose](); + } else if (this.#state === 10) { + this.#old[Symbol.dispose](); + } else if (this.#state === 11) { + this.#old[Symbol.dispose](); + } else if (this.#state === 12) { + this.#awaitee$6[Symbol.dispose](); this.#pinned[Symbol.dispose](); } else { return; } + if (this.#saved$2) { + this.#boxed[Symbol.dispose](); + } + this.#saved$2 = false; + if (this.#saved$3) { + this.#old[Symbol.dispose](); + } + this.#saved$3 = false; + if (this.#saved$4) { + this.#other[Symbol.dispose](); + } + this.#saved$4 = false; } - if (this.#saved$2) { + if (this.#saved$5) { this.#lend[Symbol.dispose](); } - this.#saved$2 = false; + this.#saved$5 = false; this.#log[Symbol.dispose](); } this.#guard[Symbol.dispose](); - if (this.#saved$3) { + if (this.#saved$6) { this.#rx[Symbol.dispose](); } - this.#saved$3 = false; + this.#saved$6 = false; } poll(_2: rt.Context): rt.Poll { @@ -425,109 +473,187 @@ export class boxedFuture { b2: { b3: { b4: { - if (this.#state === 0) { - this.#saved$3 = false; - this.#saved$2 = false; - this.#saved$3 = true; - this.#awaitee = new recvFuture$kio_Consumer(this.#rx); - } else if (this.#state === 1) { - rt.asyncFnResumed(); - } else if (this.#state !== 3) { - if (this.#state === 4) { - break b4; + b5: { + b6: { + b7: { + b8: { + if (this.#state === 0) { + this.#saved$6 = false; + this.#saved$5 = false; + this.#saved$4 = false; + this.#saved$3 = false; + this.#saved$2 = false; + this.#saved$6 = true; + this.#awaitee = new recvFuture$kio_Consumer(this.#rx); + } else if (this.#state === 1) { + rt.asyncFnResumed(); + } else if (this.#state !== 3) { + if (this.#state === 4) { + break b8; + } + if (this.#state === 5) { + break b7; + } + if (this.#state === 6) { + break b6; + } + if (this.#state === 7) { + break b5; + } + if (this.#state === 8) { + break b4; + } + if (this.#state === 9) { + break b3; + } + if (this.#state === 10) { + break b2; + } + if (this.#state === 11) { + break b1; + } + break b0; + } + const _7: rt.Poll = this.#awaitee.poll(_2); + if (_7 !== rt.Pending) { + const result: rt.u64 | undefined = _7; + rt.drop(this.#awaitee); + this.#first = option_unwrapOr$1tvry1i(result, 0); + const arg = this.#first; + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u64Display(arg, f)]; + const _17: string = rt.fmtFormat(["first ", args2[0]]); + Guard.note(this.#guard, _17); + const _28: [rt.u32, Log.Log] = [Guard.id(this.#guard), Log.clone$Clone(Guard.log(this.#guard))]; + this.#id = _28[0]; + this.#log = _28[1]; + const captured = Guard.new(rt.u32Add(this.#id, 900), Log.clone$Clone(this.#log)); + this.#lend = new rt.PollFn(rt.closure((_22: rt.Context): rt.Poll => { + Guard.note(captured, "lent"); + return 4; + }, (): void => { + captured[Symbol.dispose](); + }), rt.drop); + this.#saved$5 = true; + this.#awaitee$2 = new rt.DynFuture(this.#lend, rt.futurePoll); + } else { + this.#state = 3; + return rt.Pending; + } + } + const _45: rt.Poll = this.#awaitee$2.poll(_2); + if (_45 !== rt.Pending) { + const lent2: rt.u64 = _45; + rt.drop(this.#awaitee$2); + this.#awaitee$2 = this.#lend; + const twice: rt.Future = new rt.DynFuture(this.#awaitee$2, rt.futurePoll); + this.#saved = lent2; + this.#awaitee$3 = twice; + } else { + this.#state = 4; + return rt.Pending; + } + } + const _58: rt.Poll = this.#awaitee$3.poll(_2); + if (_58 !== rt.Pending) { + const result2: rt.u64 = _58; + rt.drop(this.#awaitee$3); + const lent3: rt.u64 = rt.u64Add(this.#saved, result2); + this.#awaitee$3 = this.#lend; + const direct: rt.Future = this.#awaitee$3; + this.#saved = lent3; + this.#awaitee$4 = direct; + } else { + this.#state = 5; + return rt.Pending; + } } - if (this.#state === 5) { - break b3; + const _70: rt.Poll = this.#awaitee$4.poll(_2); + if (_70 !== rt.Pending) { + this.#saved = rt.u64Add(this.#saved, _70); + this.#awaitee$4 = this.#lend; + } else { + this.#state = 6; + return rt.Pending; } - if (this.#state === 6) { - break b2; - } - if (this.#state === 7) { - break b1; - } - break b0; } - const _7: rt.Poll = this.#awaitee.poll(_2); - if (_7 !== rt.Pending) { - const result: rt.u64 | undefined = _7; - rt.drop(this.#awaitee); - this.#first = option_unwrapOr$1tvry1i(result, 0); - const arg = this.#first; - const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u64Display(arg, f)]; - const _17: string = rt.fmtFormat(["first ", args2[0]]); - Guard.note(this.#guard, _17); - const _28: [rt.u32, Log.Log] = [Guard.id(this.#guard), Log.clone$Clone(Guard.log(this.#guard))]; - this.#id = _28[0]; - this.#log = _28[1]; - const captured = Guard.new(rt.u32Add(this.#id, 900), Log.clone$Clone(this.#log)); - this.#lend = new rt.PollFn(rt.closure((_22: rt.Context): rt.Poll => { - Guard.note(captured, "lent"); - return 4; - }, (): void => { - captured[Symbol.dispose](); - }), rt.drop); + const _81: rt.Poll = this.#awaitee$4.poll(_2); + if (_81 !== rt.Pending) { + this.#saved = rt.u64Add(this.#saved, _81); + const arg2 = this.#saved; + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.u64Display(arg2, f)]; + const _92: string = rt.fmtFormat(["lent ", args4[0]]); + Guard.note(this.#guard, _92); + this.#saved$5 = false; + this.#lend[Symbol.dispose](); + this.#awaitee$4 = owning(Guard.new(rt.u32Add(this.#id, 10), Log.clone$Clone(this.#log))); + this.#other = owning(Guard.new(rt.u32Add(this.#id, 40), Log.clone$Clone(this.#log))); + this.#saved$4 = true; + this.#old = rewrite$futures_owning_closure0(this.#awaitee$4, this.#other, owning, this.#guard); + this.#saved$3 = true; + this.#boxed = owning(Guard.new(rt.u32Add(this.#id, 60), Log.clone$Clone(this.#log))); this.#saved$2 = true; - this.#awaitee$2 = new rt.DynFuture(this.#lend, rt.futurePoll); + const _130: rt.Future = owning(Guard.new(rt.u32Add(this.#id, 70), Log.clone$Clone(this.#log))); + put$Pin_boxed_Box_future_PollFn_futures_owning_closure0(this.#boxed, _130); + this.#awaitee$5 = this.#awaitee$4; } else { - this.#state = 3; + this.#state = 7; return rt.Pending; } } - const _45: rt.Poll = this.#awaitee$2.poll(_2); - if (_45 !== rt.Pending) { - const lent2: rt.u64 = _45; - rt.drop(this.#awaitee$2); - this.#awaitee$2 = this.#lend; - const twice: rt.Future = new rt.DynFuture(this.#awaitee$2, rt.futurePoll); - this.#saved = lent2; - this.#awaitee$3 = twice; + const _140: rt.Poll = this.#awaitee$5.poll(_2); + if (_140 !== rt.Pending) { + this.#moved = _140; + this.#awaitee$5[Symbol.dispose](); + this.#saved$3 = false; + this.#old = this.#old; } else { - this.#state = 4; + this.#state = 8; return rt.Pending; } } - const _58: rt.Poll = this.#awaitee$3.poll(_2); - if (_58 !== rt.Pending) { - const result2: rt.u64 = _58; - rt.drop(this.#awaitee$3); - const lent3: rt.u64 = rt.u64Add(this.#saved, result2); - this.#awaitee$3 = this.#lend; - const direct: rt.Future = this.#awaitee$3; - this.#saved = lent3; - this.#awaitee$4 = direct; + const _150: rt.Poll = this.#old.poll(_2); + if (_150 !== rt.Pending) { + const result6: rt.u64 = _150; + this.#old[Symbol.dispose](); + this.#moved = rt.u64Add(this.#moved, result6); + this.#saved$4 = false; + this.#old = this.#other; } else { - this.#state = 5; + this.#state = 9; return rt.Pending; } } - const _70: rt.Poll = this.#awaitee$4.poll(_2); - if (_70 !== rt.Pending) { - this.#saved = rt.u64Add(this.#saved, _70); - this.#awaitee$4 = this.#lend; + const _161: rt.Poll = this.#old.poll(_2); + if (_161 !== rt.Pending) { + const result7: rt.u64 = _161; + this.#old[Symbol.dispose](); + this.#moved = rt.u64Add(this.#moved, result7); + this.#saved$2 = false; + this.#old = this.#boxed; } else { - this.#state = 6; + this.#state = 10; return rt.Pending; } } - const _81: rt.Poll = this.#awaitee$4.poll(_2); - if (_81 !== rt.Pending) { - this.#saved = rt.u64Add(this.#saved, _81); - const arg2 = this.#saved; - const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.u64Display(arg2, f)]; - const _92: string = rt.fmtFormat(["lent ", args4[0]]); - Guard.note(this.#guard, _92); - this.#saved$2 = false; - this.#lend[Symbol.dispose](); - const _108: [Guard.Guard, Guard.Guard, Guard.Guard] = [Guard.new(rt.u32Add(this.#id, 500), Log.clone$Clone(this.#log)), Guard.new(rt.u32Add(this.#id, 600), Log.clone$Clone(this.#log)), Guard.new(rt.u32Add(this.#id, 700), Log.clone$Clone(this.#log))]; - const block: Guard.Guard = _108[0]; - const waiting: Guard.Guard = _108[1]; - const ready: Guard.Guard = _108[2]; + const _172: rt.Poll = this.#old.poll(_2); + if (_172 !== rt.Pending) { + const result8: rt.u64 = _172; + this.#old[Symbol.dispose](); + this.#moved = rt.u64Add(this.#moved, result8); + const arg3 = this.#moved; + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.u64Display(arg3, f)]; + const _183: string = rt.fmtFormat(["moved ", args6[0]]); + Guard.note(this.#guard, _183); + const _197: [Guard.Guard, Guard.Guard, Guard.Guard] = [Guard.new(rt.u32Add(this.#id, 500), Log.clone$Clone(this.#log)), Guard.new(rt.u32Add(this.#id, 600), Log.clone$Clone(this.#log)), Guard.new(rt.u32Add(this.#id, 700), Log.clone$Clone(this.#log))]; + const block: Guard.Guard = _197[0]; + const waiting: Guard.Guard = _197[1]; + const ready: Guard.Guard = _197[2]; const next: Consumer.Consumer = Consumer.clone$Clone(this.#rx); - this.#saved$3 = false; - const _131: rt.Future = new boxedFuture$closure2(this.#rx, block); + this.#saved$6 = false; + const _220: rt.Future = new boxedFuture$closure2(this.#rx, block); let captured2 = next; const captured3 = waiting; - const _134: rt.Future = new rt.DynFuture(wait$futures_boxed_closure0_closure3_u64(rt.closure((waiter: Waiter.Waiter): rt.Poll => { + const _223: rt.Future = new rt.DynFuture(wait$futures_boxed_closure0_closure3_u64(rt.closure((waiter: Waiter.Waiter): rt.Poll => { return poll_map$1vdv176(Consumer.pollRecv(captured2, waiter), (value5: rt.u64 | undefined): rt.u64 => { Guard.note(captured3, "waited"); return rt.u64Add(option_unwrapOr$1tvry1i(value5, 0), 2); @@ -537,7 +663,7 @@ export class boxedFuture { captured3[Symbol.dispose](); })), Pending.poll$Future_futures_boxed_closure0_closure3, rt.drop); const captured4 = ready; - const queue: rt.Future[] = [_131, _134, new rt.PollFn(rt.closure((_23: rt.Context): rt.Poll => { + const queue: rt.Future[] = [_220, _223, new rt.PollFn(rt.closure((_23: rt.Context): rt.Poll => { Guard.note(captured4, "ready"); return 3; }, (): void => { @@ -553,7 +679,7 @@ export class boxedFuture { let captured6 = queue; let captured7: rt.u64 = 0; let captured8 = last; - this.#awaitee$5 = new Forward.Forward(new rt.PollFn(rt.closure((cx: rt.Context): rt.Poll => { + this.#awaitee$6 = new Forward.Forward(new rt.PollFn(rt.closure((cx: rt.Context): rt.Poll => { let i: rt.usize = 0; while (i < captured6.length) { const _83: rt.Poll = pollUnsized$dyn_Future_u64(captured6[rt.idx(captured6, i)], cx); @@ -574,26 +700,29 @@ export class boxedFuture { rt.dropAll(captured6); }), rt.drop)); } else { - this.#state = 7; + this.#state = 11; return rt.Pending; } } - const _159: rt.Poll = Forward.poll$Future(this.#awaitee$5, _2); - if (_159 !== rt.Pending) { - const total: rt.u64 = _159; - this.#awaitee$5[Symbol.dispose](); - const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.u64Display(total, f)]; - const _169: string = rt.fmtFormat(["boxed ", args6[0]]); - Guard.note(this.#guard, _169); + const _248: rt.Poll = Forward.poll$Future(this.#awaitee$6, _2); + if (_248 !== rt.Pending) { + const total: rt.u64 = _248; + this.#awaitee$6[Symbol.dispose](); + const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.u64Display(total, f)]; + const _258: string = rt.fmtFormat(["boxed ", args8[0]]); + Guard.note(this.#guard, _258); this.#pinned[Symbol.dispose](); this.#saved$2 = false; + this.#saved$3 = false; + this.#saved$4 = false; + this.#saved$5 = false; this.#log[Symbol.dispose](); this.#guard[Symbol.dispose](); - this.#saved$3 = false; + this.#saved$6 = false; this.#state = 1; return total; } - this.#state = 8; + this.#state = 12; return rt.Pending; } } @@ -645,6 +774,32 @@ export class boxedFuture$closure2 { } } +/** A `poll_fn` that owns `guard`, ready with its id. */ +export function owning(guard: Guard.Guard): rt.Future { + const captured = guard; + return new rt.PollFn(rt.closure((_22: rt.Context): rt.Poll => { + return Guard.id(captured) as rt.u64; + }, (): void => { + captured[Symbol.dispose](); + }), rt.drop); +} + +/** + * `written` and `other` written whole through their `&mut`s with `poll_fn`s `make` makes + * of guards past `guard`'s id: by assignment, `mem::replace`, whose old one it returns, + * `mem::swap`, and `Cell::set`. + */ +export function rewrite$futures_owning_closure0(written: rt.Future, other: rt.Future, make: (a0: Guard.Guard) => rt.Future, guard: Guard.Guard): rt.Future { + const made: (a0: rt.u32) => rt.Future = (n: rt.u32): rt.Future => { + return make(Guard.new(rt.u32Add(Guard.id(guard), n), Log.clone$Clone(Guard.log(guard)))); + }; + put$future_PollFn_futures_owning_closure0(written, made(20)); + const old: rt.Future = rt.replaceIn(written, made(30)); + rt.ptrWrite(written, rt.replaceIn(other, written)); + rt.cellSet(rt.cellIn(other), made(50), rt.drop); + return old; +} + /** Polls `future`, sized or not, as kio's `Waiter::poll_future` does. */ export function pollUnsized$dyn_Future_u64(future: rt.Future, cx: rt.Context): rt.Poll { return future.poll(cx); diff --git a/ts/fixtures/task/src/futures.rs b/ts/fixtures/task/src/futures.rs index 58a417330..f149439e8 100644 --- a/ts/fixtures/task/src/futures.rs +++ b/ts/fixtures/task/src/futures.rs @@ -5,9 +5,9 @@ use std::cell::Cell; use std::future::{self, Future}; use std::pin::{Pin, pin}; -use std::ptr; use std::sync::Arc; use std::task::{Context, Poll, RawWakerVTable, Wake, Waker}; +use std::{mem, ptr}; use kio::Consumer; @@ -208,7 +208,8 @@ type Boxed<'a> = Pin + 'a>>; /// Boxed futures over a channel: `Box::pin` of an async fn, awaited, and of a `&mut` to a /// `poll_fn` and to a `&mut` to it, whose box drops without dropping it, and a `&mut dyn -/// Future` of a `&mut` to it, then a queue of +/// Future` of a `&mut` to it; `poll_fn`s written whole through a `&mut`, boxed or not, by +/// each way std moves one, each written one's guard dropping first; then a queue of /// `Pin>`s of an async block, kio's `Pending`, a struct of the crate's, /// and a `poll_fn`, each owning a guard. A `poll_fn` boxed in a [`Forward`] polls the /// queue in turn through [`poll_unsized`], as moq's tasks do through kio's @@ -234,6 +235,13 @@ pub async fn boxed(mut rx: Consumer, guard: Guard) -> u64 { let lent = lent + direct.await + (&mut lend).await; guard.note(&format!("lent {lent}")); drop(lend); + let mut written = owning(Guard::new(id + 10, log.clone())); + let mut other = owning(Guard::new(id + 40, log.clone())); + let old = rewrite(&mut written, &mut other, owning, &guard); + let mut boxed = Box::pin(owning(Guard::new(id + 60, log.clone()))); + put(&mut boxed, Box::pin(owning(Guard::new(id + 70, log.clone())))); + let moved = written.await + old.await + other.await + boxed.await; + guard.note(&format!("moved {moved}")); let (block, waiting, ready) = ( Guard::new(id + 500, log.clone()), Guard::new(id + 600, log.clone()), @@ -284,6 +292,28 @@ pub async fn boxed(mut rx: Consumer, guard: Guard) -> u64 { total } +/// A `poll_fn` that owns `guard`, ready with its id. +fn owning(guard: Guard) -> future::PollFn) -> Poll> { + future::poll_fn(move |_| Poll::Ready(u64::from(guard.id()))) +} + +/// `written` and `other` written whole through their `&mut`s with `poll_fn`s `make` makes +/// of guards past `guard`'s id: by assignment, `mem::replace`, whose old one it returns, +/// `mem::swap`, and `Cell::set`. +fn rewrite( + written: &mut future::PollFn, + other: &mut future::PollFn, + make: fn(Guard) -> future::PollFn, + guard: &Guard, +) -> future::PollFn { + let made = |n: u32| make(Guard::new(guard.id() + n, guard.log().clone())); + put(written, made(20)); + let old = mem::replace(written, made(30)); + mem::swap(written, other); + Cell::from_mut(other).set(made(50)); + old +} + /// Polls `future`, sized or not, as kio's `Waiter::poll_future` does. fn poll_unsized(future: Pin<&mut F>, cx: &mut Context<'_>) -> Poll { future.poll(cx) diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index abb777ca7..c65ada4e0 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -411,10 +411,11 @@ export function ptrByteSlice(p: Place, len: number, shared: boolean): By /** * `ptr::write` of `value` through a pointer to an object: `value` copied into it, one - * object deep, so every pointer to it sees the write. + * object deep, or moved by `$move`, so every pointer to it sees the write. */ export function ptrWrite(p: T, value: T): void { if (ArrayBuffer.isView(p)) (p as unknown as Uint8Array).set(value as unknown as Uint8Array); + else if (moved(value)) value.$move(p as T & Moved); else Object.assign(p, value); } @@ -426,7 +427,7 @@ export function ptrWrite(p: T, value: T): void { export function replaceIn(p: T, value: T, depth = 0): T { const old = objectCopy(p, depth); if (Array.isArray(p)) vecAssign(p, value as unknown[]); - else Object.assign(p, value); + else ptrWrite(p, value); return old; } @@ -443,9 +444,23 @@ export function objectCopy(p: T, depth = 0): T { if (depth > 0 && writesIn(p)) for (let i = 0; i < old.length; i++) old[i] = objectCopy(old[i] as object, depth - 1); return old as T; } + if (moved(p)) return p.$move(); return Object.assign(Object.create(Object.getPrototypeOf(p)) as T, p); } +/** + * An object whose class keeps its state private, where copying its fields can't reach, + * so `$move`, which no Rust field can be named, moves it: into `to`, or else into a new + * object of its class, which it returns. + */ +interface Moved { + $move(to?: this): this; +} + +function moved(p: object): p is Moved { + return "$move" in p; +} + // Bindings /** diff --git a/ts/runtime/rt/task.ts b/ts/runtime/rt/task.ts index 12a2d0753..751ad63d5 100644 --- a/ts/runtime/rt/task.ts +++ b/ts/runtime/rt/task.ts @@ -1213,6 +1213,15 @@ export class LazyCell { this.#f = f; } + /** Moves its state into `to`, or into a new cell of its class. */ + $move(to?: this): this { + const into = to ?? new (this.constructor as new (f: (() => T) | undefined) => this)(this.#f); + into.#f = this.#f; + into.#value = this.#value; + into.#init = this.#init; + return into; + } + /** `force` and `Deref::deref`: the value, which the function makes the first time. */ force(): T { if (this.#init) return this.#value as T; @@ -1398,14 +1407,22 @@ export interface Future extends Disposable { * drops at all: a borrowed `f` does not. */ export class PollFn implements Future { - readonly #f: (cx: Context) => Poll; - readonly #drop: ((f: (cx: Context) => Poll) => void) | undefined; + #f: (cx: Context) => Poll; + #drop: ((f: (cx: Context) => Poll) => void) | undefined; constructor(f: (cx: Context) => Poll, drop?: (f: (cx: Context) => Poll) => void) { this.#f = f; this.#drop = drop; } + /** Moves its function into `to`, or into a new future. */ + $move(to?: this): this { + if (to === undefined) return new PollFn(this.#f, this.#drop) as this; + to.#f = this.#f; + to.#drop = this.#drop; + return to; + } + poll(cx: Context): Poll { return this.#f(cx); } diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 3d25be2fa..8fcf0c666 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -780,6 +780,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Some(ObjectKind::Array) if self.b.t.erased(pointee).is_slice() => { Expr::rt("vecAssign", vec![target, value]) } + Some(_) if self.b.t.moves_private(pointee) => Expr::rt("ptrWrite", vec![target, value]), Some(_) => Expr::ident("Object").member("assign").call(vec![target, value]), None => self.err(span, format!("an assignment through a reference to `{pointee}`")), }, @@ -1305,12 +1306,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } } - /// The `const` item of the thread-local `c` borrows, as `with`'s `&'static self` is: a - /// promoted borrow of the key. + /// The `const` item of the thread-local `c` is, or borrows, as `with`'s `&'static self` + /// is: a promoted borrow of the key. Each copy of a key names the same storage. fn local_key_of(&self, c: &mir::ConstOperand<'tcx>) -> Option { let tcx = self.tcx; - let ty::Ref(_, key, _) = c.ty().kind() else { - return None; + let key = match c.ty().kind() { + ty::Ref(_, key, _) => *key, + _ => c.ty(), }; let ty::Adt(adt, _) = key.kind() else { return None; @@ -1321,7 +1323,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let mir::Const::Unevaluated(uv, _) = c.const_ else { return None; }; - let promoted = &tcx.promoted_mir(uv.def)[uv.promoted?]; + let Some(promoted) = uv.promoted else { + return (key == c.ty()).then_some(uv.def); + }; + let promoted = &tcx.promoted_mir(uv.def)[promoted]; promoted .basic_blocks .iter() diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index b47e37ee3..898c950de 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1333,13 +1333,26 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// Whether a JS value of `ty` is a copy that no JS value can point at, such as a number /// or a reference, or an object its clones may share, as an `Arc`'s and a `Waker`'s are, /// pinned or boxed too, which JS erases, so a `&mut` to it is an `rt.Place`: where the - /// value is, read and written through `v`. + /// value is, read and written through `v`. A `LocalKey`'s copies share its storage too. pub fn needs_place(&self, ty: Ty<'tcx>) -> bool { use library::Std; if self.object_kind(ty).is_none() { return true; } - matches!(self.std_of(self.erased(ty)), Some(Std::Arc | Std::Waker)) + matches!( + self.std_of(self.erased(ty)), + Some(Std::Arc | Std::Waker | Std::LocalKey) + ) + } + + /// Whether `ty`'s JS value, through the `Pin`s and `Box`es JS erases, is of an `rt` class + /// that keeps its state private, which `rt.ptrWrite` moves in by its `$move`. + pub fn moves_private(&self, ty: Ty<'tcx>) -> bool { + use library::Std; + matches!( + self.std_of(self.erased(ty)), + Some(Std::PollFn | Std::LazyCell | Std::LazyLock) + ) } /// What JS keeps of a `ty`: what it holds through any number of `Pin`s and `Box`es. @@ -1356,20 +1369,14 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// The kind of a `pointee`'s object, through the `Pin`s and `Box`es JS erases, where a /// whole value of it can be written into the object in place: one only its owner - /// holds, unlike an `Arc`'s, which its clones share, and whose parts `Object.assign` - /// copies, unlike a binding's, the private fields of an `rt.PollFn`, an `rt.LazyCell`, - /// an `rt.LocalKey`, or a future, or a trait object's, whatever class it is. + /// holds, unlike an `Arc`'s or a `LocalKey`'s, which its copies share, and whose state + /// a write reaches, unlike a binding's, or a future's, or a trait object's, whatever + /// class it is. pub fn write_in(&self, pointee: Ty<'tcx>) -> Option { use library::Std; let erased = self.erased(pointee); let hidden = match erased.kind() { - ty::Adt(adt, _) => { - self.bound_drops(adt.did()).is_some() - || matches!( - Std::of(self.tcx, adt.did()), - Some(Std::PollFn | Std::LazyCell | Std::LazyLock | Std::LocalKey) - ) - } + ty::Adt(adt, _) => self.bound_drops(adt.did()).is_some(), ty::Dynamic(..) | ty::Closure(..) => true, // A `Uint8Array`'s length never changes, as a boxed slice's may. ty::Slice(elem) => matches!(elem.kind(), ty::Uint(ty::UintTy::U8)), @@ -1379,7 +1386,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let shared = match erased.kind() { ty::Adt(adt, _) => matches!( Std::of(self.tcx, adt.did()), - Some(Std::Arc | Std::Waker | Std::Context | Std::MutexGuard | Std::Ref | Std::RefMut) + Some(Std::Arc | Std::Waker | Std::LocalKey | Std::Context | Std::MutexGuard | Std::Ref | Std::RefMut) ), ty::Ref(..) | ty::Coroutine(..) => true, _ => false,