diff --git a/quest/m3/ts-unsafe/README.md b/quest/m3/ts-unsafe/README.md index e54d29d86..8ec3e33af 100644 --- a/quest/m3/ts-unsafe/README.md +++ b/quest/m3/ts-unsafe/README.md @@ -79,5 +79,4 @@ Public API: the translator's output. - [Unchecked integer operations](/quest/m3/ts-unsafe/unchecked-ops.md) - `unchecked_*` and `NonZero::new_unchecked` translate, aborting on a violated precondition - [NonZero's unchecked arithmetic](/quest/m3/ts-unsafe/nonzero-unchecked-ops.md) - `NonZero::unchecked_add` and `unchecked_mul` translate, aborting at std's site - [UnsafeCell's raw accessors](/quest/m3/ts-unsafe/unsafecell.md) - `UnsafeCell::raw_get`, `from_mut`, and `replace` translate -- [A Box of one uninitialized value](/quest/m3/ts-unsafe/box-new-uninit.md) - `Box::new_uninit` and `new_zeroed` for one value translate, through `write` and `assume_init` - [A byte local whose address is taken, in a buffer of its own](/quest/m3/ts-unsafe/lone-byte-buffers.md) - raw and mutable slices of a `u8` local, and casts of them to byte arrays, translate instead of being refused diff --git a/quest/m3/ts-unsafe/box-new-uninit.md b/quest/m3/ts-unsafe/box-new-uninit.md deleted file mode 100644 index c29b39d38..000000000 --- a/quest/m3/ts-unsafe/box-new-uninit.md +++ /dev/null @@ -1,20 +0,0 @@ -# [S] A Box of one uninitialized value - -## Goal - -[rust2-ts](/ts/README.md) translates `Box::new_uninit` and `Box::new_zeroed` for one -value, written through `Box>::write` or its pointer and taken by -`assume_init`, proven by differential tests against native Rust, including drops. - -## Plan - -Suggested on [#606](https://github.com/moq-dev/rust2/pull/606), which translated the slice -forms (`Box::new_uninit_slice`, `write_clone_of_slice`). `ts/src/lower/library.rs` maps -`new_uninit_slice` and `assume_init`, and `pointer::allocates` already names -`new_uninit` and `new_zeroed`, but neither single-value constructor is mapped. Check -which of `assume_init` and `write` already cover a single value before adding them. - -Decided with the maintainer on 2026-11-07: ✅ m3, in this epic (rejected: m2, drop). No -consumer at the moq pin. - -Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index a64f2f0b1..36b95fd00 100644 --- a/ts/README.md +++ b/ts/README.md @@ -111,7 +111,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 `rs.String` it owns or an `rs.Borrowed` holding the string it borrows, whose text `rs.strCowStr` reads | | `&T`, `&mut T`, `Box`, `Reverse`, `NonZero`, `AssertUnwindSafe` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rs.Place`, which for what a box holds is the place holding the box | | `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, or copy the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. A pointer to a slice cast to one to an array of its elements is the `rs.Slice` or `rs.ByteSlice` of that many from its first, read through only where Rust reads. A `*const ()` is an identity, such as a waker's data, and `rs.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rs.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rs.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rs` class such as `rs.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | -| `MaybeUninit` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined`, as each of `Box::new_uninit_slice`'s is; a slice's `write_clone_of_slice` clones into its slots without dropping what they held | +| `MaybeUninit` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined`, as `Box::new_uninit` and each of `Box::new_uninit_slice`'s is; a slice's `write_clone_of_slice` clones into its slots without dropping what they held. `Box::new_zeroed`'s holds the value of zero bytes in each part they are one of, such as a number, `false`, `None`, a null pointer, or an enum's variant of a tag or niche of zero, and `undefined` in each part they are none of, such as a reference, for a write to fill before a read | | `ManuallyDrop` | `T`, which nothing drops but its own `drop`; its `Debug`, comparisons, and constants are refused | | `union` | an object of slots named by offset, `at0` and `at8`, each holding the field part kept there; fields must overlap in the same type, or in a non-null pointer and an `Option` of it | | an iterator | `rs.Iter`, whose adaptors are runtime functions: `rs.iterMap(rs.iterSlice(v), f)` | diff --git a/ts/fixtures/lang/manual/expected/core/option.ts b/ts/fixtures/lang/manual/expected/core/option.ts index 3674329fe..abb59ad3d 100644 --- a/ts/fixtures/lang/manual/expected/core/option.ts +++ b/ts/fixtures/lang/manual/expected/core/option.ts @@ -2,6 +2,13 @@ import * as rs from "../rs.ts"; +export function unwrapOr$1fr498u(self: rs.u8 | undefined, default_: rs.u8): rs.u8 { + if (self === undefined) { + return default_; + } + return self; +} + export function map$1yjmt5m(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.u8): rs.u8 | undefined { if (self === undefined) { return undefined; diff --git a/ts/fixtures/lang/manual/expected/lib.ts b/ts/fixtures/lang/manual/expected/lib.ts index 02a257938..fde5ceb36 100644 --- a/ts/fixtures/lang/manual/expected/lib.ts +++ b/ts/fixtures/lang/manual/expected/lib.ts @@ -2,7 +2,15 @@ import * as rs from "./rs.ts"; import { arcs, needsDrops } from "./manual.ts"; -import { uninits, boxedUninits } from "./uninit.ts"; +import { uninits, boxedUninits, boxedOne, boxedZeroed } from "./uninit.ts"; + +export function copy$array_array2_u16(v: rs.u16[][]): rs.u16[][] { + return v.map(copy$array_u16); +} + +export function copy$array_u16(v: rs.u16[]): rs.u16[] { + return v.slice(); +} export function copy$array_u8(v: Uint8Array): Uint8Array { return v.slice(); @@ -32,6 +40,10 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By uninits(data3, out3); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["uninit::boxed_uninits", (_25: rs.u8, data4: rs.ByteSlice, out4: rs.ByteVec): undefined => { boxedUninits(data4, out4); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["uninit::boxed_one", (_26: rs.u8, data5: rs.ByteSlice, out5: rs.ByteVec): undefined => { + boxedOne(data5, out5); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["uninit::boxed_zeroed", (_27: rs.u8, data6: rs.ByteSlice, out6: rs.ByteVec): undefined => { + boxedZeroed(data6, out6); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/manual/expected/uninit.Held.ts b/ts/fixtures/lang/manual/expected/uninit.Held.ts new file mode 100644 index 000000000..600a466de --- /dev/null +++ b/ts/fixtures/lang/manual/expected/uninit.Held.ts @@ -0,0 +1,11 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** + * An enum whose variant of zero bytes may hold a reference, which zero bytes are no + * value of. + */ +export type Held = { tag: "Lent"; _0: rs.u8; _1: rs.u16 } | { tag: "Count"; _0: rs.u32 }; + +//# sourceMappingURL=uninit.Held.ts.map diff --git a/ts/fixtures/lang/manual/expected/uninit.Pair.ts b/ts/fixtures/lang/manual/expected/uninit.Pair.ts new file mode 100644 index 000000000..cd892c948 --- /dev/null +++ b/ts/fixtures/lang/manual/expected/uninit.Pair.ts @@ -0,0 +1,11 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** A struct written field by field through a pointer to it. */ +export interface Pair { + tag: rs.u8; + name: rs.String; +} + +//# sourceMappingURL=uninit.Pair.ts.map diff --git a/ts/fixtures/lang/manual/expected/uninit.Tag.ts b/ts/fixtures/lang/manual/expected/uninit.Tag.ts new file mode 100644 index 000000000..11a04ca10 --- /dev/null +++ b/ts/fixtures/lang/manual/expected/uninit.Tag.ts @@ -0,0 +1,10 @@ +// Generated by rust2-ts. Do not edit. + +export function eq$cmp_PartialEq(self: Tag, other: Tag): boolean { + return { One: 1, Zero: 0 }[self] === { One: 1, Zero: 0 }[other]; +} + +/** One of `Tag`'s variants is zero bytes, though not its first. */ +export type Tag = "One" | "Zero"; + +//# sourceMappingURL=uninit.Tag.ts.map diff --git a/ts/fixtures/lang/manual/expected/uninit.Zeroed.ts b/ts/fixtures/lang/manual/expected/uninit.Zeroed.ts new file mode 100644 index 000000000..7b7409008 --- /dev/null +++ b/ts/fixtures/lang/manual/expected/uninit.Zeroed.ts @@ -0,0 +1,54 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Tag from "./uninit.Tag.ts"; + +/** + * Every part zero bytes are a value of, a value that counts its drops through one it may + * not hold, and an array whose length is past what an array literal would spell. + */ +export class Zeroed { + n: rs.u32; + on: boolean; + c: rs.char; + f: rs.f64; + wide: rs.u64; + signed: rs.i128; + words: rs.u16[]; + many: rs.u32[]; + pair: [rs.u8, rs.i64]; + tag: Tag.Tag; + maybe: Tag.Tag | undefined; + p: rs.Place; + seen: rs.Cell<[rs.u8, rs.u8]> | undefined; + + constructor(n: rs.u32, on: boolean, c: rs.char, f: rs.f64, wide: rs.u64, signed: rs.i128, words: rs.u16[], many: rs.u32[], pair: [rs.u8, rs.i64], tag: Tag.Tag, maybe: Tag.Tag | undefined, p: rs.Place, seen: rs.Cell<[rs.u8, rs.u8]> | undefined) { + this.n = n; + this.on = on; + this.c = c; + this.f = f; + this.wide = wide; + this.signed = signed; + this.words = words; + this.many = many; + this.pair = pair; + this.tag = tag; + this.maybe = maybe; + this.p = p; + this.seen = seen; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function drop$ops_Drop(self: Zeroed): void { + if (self.seen !== undefined) { + const seen: rs.Cell<[rs.u8, rs.u8]> = self.seen; + const _6: [rs.u8, rs.u8] = seen.v; + rs.cellSet(seen, [_6[0], rs.u8WrappingAdd(_6[1], 1)] as [rs.u8, rs.u8]); + } +} + +//# sourceMappingURL=uninit.Zeroed.ts.map diff --git a/ts/fixtures/lang/manual/expected/uninit.ts b/ts/fixtures/lang/manual/expected/uninit.ts index 9bade7812..3c45b04f2 100644 --- a/ts/fixtures/lang/manual/expected/uninit.ts +++ b/ts/fixtures/lang/manual/expected/uninit.ts @@ -1,8 +1,13 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { map$1yjmt5m as option_map$1yjmt5m } from "./core/option.ts"; +import { copy$array_array2_u16, copy$array_u16 } from "./lib.ts"; +import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1yjmt5m as option_map$1yjmt5m } from "./core/option.ts"; import * as Counted from "./uninit.Counted.ts"; +import type * as Held from "./uninit.Held.ts"; +import type * as Pair from "./uninit.Pair.ts"; +import * as Tag from "./uninit.Tag.ts"; +import * as Zeroed from "./uninit.Zeroed.ts"; /** * An array of bytes not yet written, a `Vec` of them, and an array of `u16`s, each @@ -215,4 +220,251 @@ export function boxedClone$uninit_Counted(src: rs.Slice): rs.Sl return slots; } +/** + * Boxes of one value not yet written: written through what `MaybeUninit::write` lends, + * through its pointer, field by field, by `Box::write`, and through a raw pointer from + * `Box::into_raw`, then read as what they hold; a box never written, or written and + * never taken by `assume_init`, drops nothing. + */ +export function boxedOne(data: rs.ByteSlice, out: rs.ByteVec): void { + const first: rs.u8 = option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0); + let word: rs.u16 = (undefined as unknown) as rs.u16; + const _9: rs.u16 = first as rs.u16; + const uninit = rs.place(() => word, (value) => word = value); + uninit.v = _9; + const _7: rs.Place = uninit; + _7.v = rs.u16Add(_7.v, 257); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u16ToBeBytes(word).reverse())); + let byte: rs.u8 = (0 as unknown) as rs.u8; + byte = first ^ 85; + rs.byteVecPush(out, byte); + rs.byteVecExtendFromSlice(out, rs.stringAsBytes(rs.fmtFormat([(f: rs.Formatter) => rs.u8LowerHex(first, f)]).v)); + const pair: Pair.Pair = ({ tag: undefined, name: undefined } as unknown) as Pair.Pair; + const p: Pair.Pair = pair; + p.tag = first; + p.name = rs.stringFrom("pair"); + const pair2: Pair.Pair = pair; + rs.byteVecPush(out, pair2.tag); + rs.byteVecExtendFromSlice(out, rs.stringAsBytes(pair2.name.v)); + const slot: Pair.Pair = ({ tag: undefined, name: undefined } as unknown) as Pair.Pair; + const p2: Pair.Pair = slot; + const _58: rs.String = rs.stringFrom("kept"); + const uninit2 = slot; + rs.ptrWrite(uninit2, { tag: first, name: _58 }); + const kept: Pair.Pair = uninit2; + rs.byteVecPush(out, p2 === kept ? 1 : 0); + rs.byteVecExtendFromSlice(out, rs.stringAsBytes(kept.name.v)); + const slots: rs.u16[] = (new Array(3) as unknown) as rs.u16[]; + const p3: rs.Slice = rs.sliceFrom(slots); + const uninit3 = slots; + rs.ptrWrite(uninit3, [1, 2, first as rs.u16]); + const kept2: rs.u16[] = uninit3; + rs.byteVecPush(out, rs.sliceSame(p3, rs.sliceFrom(kept2)) ? 1 : 0); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u16ToBeBytes(kept2[2]).reverse())); + const words: rs.u16[] = (new Array(3) as unknown) as rs.u16[]; + const uninit4 = words; + rs.ptrWrite(uninit4, [first as rs.u16, 2, 3]); + const _88: rs.Slice = rs.sliceFrom(uninit4); + _88.data[_88.start + 1] = _88.data[_88.start + 1] ^ 255; + const iter: rs.Iter = rs.iterSlice(copy$array_u16(words)); + while (true) { + const _98: rs.u16 | rs.Done = iter.next(); + if (_98 === rs.DONE) { + break; + } + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u16ToBeBytes(_98).reverse())); + } + const raw: rs.Place = { v: (undefined as unknown) as rs.u32 }; + raw.v = ((first as rs.u32) << 20) >>> 0; + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes(raw.v).reverse())); + const counts: rs.Cell<[rs.u8, rs.u8]> = new rs.Cell([0, 0] as [rs.u8, rs.u8]); + const counted: Counted.Counted = (new Counted.Counted((undefined as unknown) as rs.u8, (undefined as unknown) as rs.Cell<[rs.u8, rs.u8]>) as unknown) as Counted.Counted; + rs.ptrWrite(counted, new Counted.Counted(first, counts)); + const counted2: Counted.Counted = counted; + rs.byteVecPush(out, counted2._0); + counted2[Symbol.dispose](); + rs.byteVecPush(out, counts.v[1]); + (new Counted.Counted((undefined as unknown) as rs.u8, (undefined as unknown) as rs.Cell<[rs.u8, rs.u8]>) as unknown) as Counted.Counted; + rs.ptrWrite((new Counted.Counted((undefined as unknown) as rs.u8, (undefined as unknown) as rs.Cell<[rs.u8, rs.u8]>) as unknown) as Counted.Counted, new Counted.Counted(first, counts)); + rs.byteVecPush(out, counts.v[1]); + rs.byteVecPush(out, boxed$u8(rs.u8WrappingAdd(first, 1))); + const _154: Counted.Counted = boxed$uninit_Counted(new Counted.Counted(first, counts)); + rs.byteVecPush(out, _154._0); + _154[Symbol.dispose](); + rs.byteVecPush(out, counts.v[1]); +} + +/** `value` boxed by way of a box not yet written. */ +export function boxed$u8(value: rs.u8): rs.u8 { + let slot: rs.u8 = (0 as unknown) as rs.u8; + const uninit = rs.place(() => slot, (value2) => slot = value2); + uninit.v = value; + uninit; + return slot; +} + +/** `value` boxed by way of a box not yet written. */ +export function boxed$uninit_Counted(value: Counted.Counted): Counted.Counted { + const slot: Counted.Counted = (new Counted.Counted((undefined as unknown) as rs.u8, (undefined as unknown) as rs.Cell<[rs.u8, rs.u8]>) as unknown) as Counted.Counted; + const uninit = slot; + rs.ptrWrite(uninit, value); + uninit; + return slot; +} + +/** + * Boxes of zero bytes: every part zero bytes are a value of reads as that value, an + * enum as the variant zero bytes are, and a part they are no value of, such as a + * reference or a `String`, is written before it is read; a box never taken by + * `assume_init` drops nothing. + */ +export function boxedZeroed(data: rs.ByteSlice, out: rs.ByteVec): void { + let value!: Held.Held; + const first: rs.u8 = option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes((0 as unknown) as rs.u32).reverse())); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.i64ToBeBytes((0 as unknown) as rs.i64).reverse())); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u128ToBeBytes((0 as unknown) as rs.u128).reverse())); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.f32ToLeBytes((0.0 as unknown) as rs.f32))); + rs.byteVecPush(out, (false as unknown) as boolean ? 1 : 0); + rs.byteVecPush(out, ((0 as unknown) as rs.char) & 0xff); + rs.byteVecPush(out, ((rs.NULL as unknown) as rs.Place) === rs.NULL ? 1 : 0); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(((undefined as unknown) as rs.u8 | undefined) === undefined ? 1 : 0, ((false as unknown) as boolean | undefined) === false ? 1 : 0, ((undefined as unknown) as rs.u16 | undefined) === undefined ? 1 : 0, ((undefined as unknown) as rs.u8 | undefined) === undefined ? 1 : 0, ((undefined as unknown) as rs.u8 | undefined) === undefined ? 1 : 0, ((0 as unknown) as rs.Ordering) === 0 ? 1 : 0, !Tag.eq$cmp_PartialEq((("Zero" as Tag.Tag) as unknown) as Tag.Tag, "One" as Tag.Tag) ? 1 : 0, rs.eqOption(Tag.eq$cmp_PartialEq)((("Zero" as Tag.Tag) as unknown) as Tag.Tag | undefined, "Zero") ? 1 : 0)), undefined, true); + const cell: rs.Cell = (new rs.Cell(0) as unknown) as rs.Cell; + rs.cellSet(cell, rs.u16Add(cell.v, first as rs.u16)); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u16ToBeBytes(cell.v).reverse())); + const atomic: rs.Atomic = (new rs.Atomic(0) as unknown) as rs.Atomic; + rs.atomicFetch(atomic, rs.u32WrappingAdd, ((first as rs.u32) << 8) >>> 0, 0); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes(rs.atomicLoad(atomic, 0)).reverse())); + rs.byteVecPush(out, rs.u8WrappingSub((0 as unknown) as rs.u8, first)); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u128ToBeBytes(rs.durationAsMicros(rs.durationAdd(({ secs: 0, nanos: 0 } as unknown) as rs.Duration, rs.durationFromMillis(first as rs.u64)))).reverse())); + const shared: rs.RefCell = (new rs.RefCell(0) as unknown) as rs.RefCell; + const _149: rs.u16 = first as rs.u16; + const _152: rs.RefCell = rs.refCellBorrowMut(shared); + _152.value = rs.u16Add(_152.value, _149); + rs.refMutRelease(_152); + const _162: rs.RefCell = rs.refCellBorrow(shared); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u16ToBeBytes(_162.value).reverse())); + rs.refRelease(_162); + const locked: rs.Mutex = (new rs.Mutex(0) as unknown) as rs.Mutex; + const _168: rs.Mutex = rs.mutexLock(locked); + _168.value = _168.value ^ first; + rs.mutexUnlock(_168); + const _175: rs.Mutex = rs.mutexLock(locked); + rs.byteVecPush(out, _175.value); + rs.mutexUnlock(_175); + let wrapped2: [rs.u32, rs.u8] = (([0, (undefined as unknown) as rs.u8] as [rs.u32, rs.u8]) as unknown) as [rs.u32, rs.u8]; + wrapped2[1] = first; + const _186: [rs.u32, rs.u8] = wrapped2; + const n: rs.u32 = _186[0]; + const lent: rs.u8 = _186[1]; + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes(n).reverse())); + rs.byteVecPush(out, lent); + const cell2: rs.Cell<[rs.u32, rs.u8]> = (new rs.Cell([0, (undefined as unknown) as rs.u8] as [rs.u32, rs.u8]) as unknown) as rs.Cell<[rs.u32, rs.u8]>; + cell2.v[1] = first; + const _203: [rs.u32, rs.u8] = cell2.v; + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes(_203[0]).reverse())); + rs.byteVecPush(out, _203[1]); + const iter: rs.Iter = rs.iterSlice([{ tag: "Lent", _0: first, _1: 7 } as Held.Held, { tag: "Count", _0: rs.u32Add(first as rs.u32, 1) } as Held.Held]); + while (true) { + const _222: Held.Held | rs.Done = iter.next(); + if (_222 === rs.DONE) { + break; + } + value = _222; + let held: Held.Held = (({ tag: "Lent", _0: (undefined as unknown) as rs.u8, _1: 0 } as Held.Held) as unknown) as Held.Held; + const uninit = rs.place(() => held, (value6) => held = value6); + uninit.v = value; + uninit; + const _229: Held.Held = held; + if (_229.tag === "Lent") { + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(_229._0, _229._1 & 0xff)), undefined, true); + } else { + rs.byteVecPush(out, _229._0 & 0xff); + } + } + const bytes: Uint8Array = (new Uint8Array(16) as unknown) as Uint8Array; + rs.ptrAdd(rs.byteSliceAsPtr(rs.byteSliceFrom(bytes)), 3).v = first; + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(bytes)); + const grid: rs.u16[][] = (Array.from({ length: 3 }, (): rs.u16[] => { + return Array.from({ length: 2 }, (): rs.u16 => { + return 0; + }); + }) as unknown) as rs.u16[][]; + const _250: rs.u16 = rs.u16Add(first as rs.u16, 1); + const _251: rs.Slice = rs.sliceFrom(grid); + _251.data[_251.start + 1][0] = _250; + const iter2: rs.Iter = rs.iterSlice(copy$array_array2_u16(grid)); + while (true) { + const _261: rs.u16[] | rs.Done = iter2.next(); + if (_261 === rs.DONE) { + break; + } + rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(copy$array_u16(_261)), (w: rs.u16): Uint8Array => { + return rs.u16ToBeBytes(w).reverse(); + }, (value: Uint8Array) => rs.iterSlice(value), true, 2), undefined, true); + } + const _273: [rs.u8, rs.i32, boolean] = (([0, 0, false] as [rs.u8, rs.i32, boolean]) as unknown) as [rs.u8, rs.i32, boolean]; + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(_273[0], _273[1] & 0xff, _273[2] ? 1 : 0)), undefined, true); + const lent2: [rs.u32, rs.u8] = (([0, (undefined as unknown) as rs.u8] as [rs.u32, rs.u8]) as unknown) as [rs.u32, rs.u8]; + lent2[1] = first; + const lent3: [rs.u32, rs.u8] = lent2; + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes(lent3[0]).reverse())); + rs.byteVecPush(out, lent3[1]); + const named: [rs.String, rs.u16] = (([(undefined as unknown) as rs.String, 0] as [rs.String, rs.u16]) as unknown) as [rs.String, rs.u16]; + const _296: [rs.String, rs.u16] = named; + _296[0] = rs.fmtFormat([(f: rs.Formatter) => rs.u8Display(first, f)]); + const named2: [rs.String, rs.u16] = named; + rs.byteVecExtendFromSlice(out, rs.stringAsBytes(named2[0].v)); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u16ToBeBytes(named2[1]).reverse())); + const counts: rs.Cell<[rs.u8, rs.u8]> = new rs.Cell([0, 0] as [rs.u8, rs.u8]); + const zeroed: Zeroed.Zeroed = (new Zeroed.Zeroed(0, false, 0, 0.0, 0, 0, Array.from({ length: 3 }, (): rs.u16 => { + return 0; + }), Array.from({ length: 300 }, (): rs.u32 => { + return 0; + }), [0, 0], "Zero", "Zero", rs.NULL, undefined) as unknown) as Zeroed.Zeroed; + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes(zeroed.n).reverse())); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(zeroed.on ? 1 : 0, zeroed.c & 0xff, rs.u8FromF64(zeroed.f), rs.u64Low(zeroed.wide) & 0xff, rs.i128Low(zeroed.signed) & 0xff)), undefined, true); + rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(zeroed.words), (w2: rs.u16): Uint8Array => { + return rs.u16ToBeBytes(w2).reverse(); + }, (value: Uint8Array) => rs.iterSlice(value), true, 2), undefined, true); + rs.byteVecPush(out, rs.iterFold(rs.iterSlice(zeroed.many), 0, (n5: rs.u32, _39: rs.u32): rs.u32 => { + return (n5 | _39) >>> 0; + }) & 0xff); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(zeroed.pair[0], rs.i64Low(zeroed.pair[1]) & 0xff, { One: 1, Zero: 0 }[zeroed.tag] & 0xff)), undefined, true); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(rs.eqOption(Tag.eq$cmp_PartialEq)(zeroed.maybe, "Zero") ? 1 : 0, zeroed.p === rs.NULL ? 1 : 0)), undefined, true); + zeroed[Symbol.dispose](); + const seen: Zeroed.Zeroed = (new Zeroed.Zeroed(0, false, 0, 0.0, 0, 0, Array.from({ length: 3 }, (): rs.u16 => { + return 0; + }), Array.from({ length: 300 }, (): rs.u32 => { + return 0; + }), [0, 0], "Zero", "Zero", rs.NULL, undefined) as unknown) as Zeroed.Zeroed; + const p: Zeroed.Zeroed = seen; + p.seen = counts; + rs.arrayAsPtr(p.many, 299).v = first as rs.u32; + const seen2: Zeroed.Zeroed = seen; + rs.byteVecPush(out, seen2.many[299] & 0xff); + seen2[Symbol.dispose](); + rs.byteVecPush(out, counts.v[1]); + ((new Zeroed.Zeroed(0, false, 0, 0.0, 0, 0, Array.from({ length: 3 }, (): rs.u16 => { + return 0; + }), Array.from({ length: 300 }, (): rs.u32 => { + return 0; + }), [0, 0], "Zero", "Zero", rs.NULL, undefined) as unknown) as Zeroed.Zeroed).seen = counts; + undefined; + rs.byteVecPush(out, counts.v[1]); + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u64ToBeBytes(zeroedBox$u64()).reverse())); + const _409: [rs.u8, rs.u16] = zeroedBox$tuple2_u8_u16(); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(_409[0], _409[1] & 0xff)), undefined, true); +} + +/** A box of zero bytes, taken as a `T`. */ +export function zeroedBox$tuple2_u8_u16(): [rs.u8, rs.u16] { + return (([0, 0] as [rs.u8, rs.u16]) as unknown) as [rs.u8, rs.u16]; +} + +/** A box of zero bytes, taken as a `T`. */ +export function zeroedBox$u64(): rs.u64 { + return (0 as unknown) as rs.u64; +} + //# sourceMappingURL=uninit.ts.map diff --git a/ts/fixtures/lang/manual/src/lib.rs b/ts/fixtures/lang/manual/src/lib.rs index edf72c2ac..4c9201499 100644 --- a/ts/fixtures/lang/manual/src/lib.rs +++ b/ts/fixtures/lang/manual/src/lib.rs @@ -21,5 +21,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("manual::needs_drops", |_, data, out| manual::needs_drops(data, out)), ("uninit::uninits", |_, data, out| uninit::uninits(data, out)), ("uninit::boxed_uninits", |_, data, out| uninit::boxed_uninits(data, out)), + ("uninit::boxed_one", |_, data, out| uninit::boxed_one(data, out)), + ("uninit::boxed_zeroed", |_, data, out| uninit::boxed_zeroed(data, out)), ] } diff --git a/ts/fixtures/lang/manual/src/uninit.rs b/ts/fixtures/lang/manual/src/uninit.rs index ac76453a8..9eb0e0ad8 100644 --- a/ts/fixtures/lang/manual/src/uninit.rs +++ b/ts/fixtures/lang/manual/src/uninit.rs @@ -156,3 +156,272 @@ fn boxed_clone(src: &[T]) -> Box<[T]> { slots.write_clone_of_slice(src); unsafe { slots.assume_init() } } + +/// Boxes of one value not yet written: written through what `MaybeUninit::write` lends, +/// through its pointer, field by field, by `Box::write`, and through a raw pointer from +/// `Box::into_raw`, then read as what they hold; a box never written, or written and +/// never taken by `assume_init`, drops nothing. +pub fn boxed_one(data: &[u8], out: &mut Vec) { + let first = data.first().copied().unwrap_or(0); + let mut word = Box::::new_uninit(); + *word.write(u16::from(first)) += 0x101; + let word = unsafe { word.assume_init() }; + out.extend_from_slice(&word.to_le_bytes()); + + let mut byte = Box::::new_uninit(); + unsafe { byte.as_mut_ptr().write(first ^ 0x55) }; + out.push(*unsafe { byte.assume_init() }); + + let text = Box::write(Box::new_uninit(), format!("{first:x}")); + out.extend_from_slice(text.as_bytes()); + + let mut pair = Box::::new_uninit(); + let p = pair.as_mut_ptr(); + unsafe { + (&raw mut (*p).tag).write(first); + (&raw mut (*p).name).write(String::from("pair")); + } + let pair = unsafe { pair.assume_init() }; + out.push(pair.tag); + out.extend_from_slice(pair.name.as_bytes()); + + // `Box::write` writes into the box, where a pointer taken before it still points. + let mut slot = Box::::new_uninit(); + let p = slot.as_mut_ptr(); + let kept = Box::write( + slot, + Pair { + tag: first, + name: String::from("kept"), + }, + ); + out.push(u8::from(std::ptr::eq(p, &*kept))); + out.extend_from_slice(kept.name.as_bytes()); + let mut slots = Box::<[u16; 3]>::new_uninit(); + let p = slots.as_mut_ptr(); + let kept = Box::write(slots, [1, 2, u16::from(first)]); + out.push(u8::from(std::ptr::eq(p, &*kept))); + out.extend_from_slice(&kept[2].to_le_bytes()); + + let mut words = Box::<[u16; 3]>::new_uninit(); + words.write([u16::from(first), 2, 3])[1] ^= 0xff; + for w in *unsafe { words.assume_init() } { + out.extend_from_slice(&w.to_le_bytes()); + } + + let raw = Box::into_raw(Box::::new_uninit()); + unsafe { raw.cast::().write(u32::from(first) << 20) }; + let back = unsafe { Box::from_raw(raw).assume_init() }; + out.extend_from_slice(&back.to_le_bytes()); + + let counts = std::cell::Cell::new((0, 0)); + let mut counted = Box::::new_uninit(); + counted.write(Counted(first, &counts)); + let counted = unsafe { counted.assume_init() }; + out.push(counted.0); + drop(counted); + out.push(counts.get().1); + drop(Box::::new_uninit()); + let mut leaked = Box::::new_uninit(); + leaked.write(Counted(first, &counts)); + drop(leaked); + out.push(counts.get().1); + + // Made by a generic function, of bytes and of values that count their drops. + out.push(*boxed(first.wrapping_add(1))); + out.push(boxed(Counted(first, &counts)).0); + out.push(counts.get().1); +} + +/// A struct written field by field through a pointer to it. +struct Pair { + tag: u8, + name: String, +} + +/// `value` boxed by way of a box not yet written. +fn boxed(value: T) -> Box { + let mut slot = Box::new_uninit(); + slot.write(value); + unsafe { slot.assume_init() } +} + +/// One of `Tag`'s variants is zero bytes, though not its first. +#[derive(Debug, Clone, Copy, PartialEq)] +enum Tag { + One = 1, + Zero = 0, +} + +/// An enum whose variant of zero bytes may hold a reference, which zero bytes are no +/// value of. +enum Held<'a> { + Lent(&'a u8, u16), + Count(u32), +} + +/// Every part zero bytes are a value of, a value that counts its drops through one it may +/// not hold, and an array whose length is past what an array literal would spell. +struct Zeroed<'a> { + n: u32, + on: bool, + c: char, + f: f64, + wide: u64, + signed: i128, + words: [u16; 3], + many: [u32; 300], + pair: (u8, i64), + tag: Tag, + maybe: Option, + p: *const u8, + seen: Option<&'a std::cell::Cell<(u8, u8)>>, +} + +impl Drop for Zeroed<'_> { + fn drop(&mut self) { + if let Some(seen) = self.seen { + let (c, d) = seen.get(); + seen.set((c, d.wrapping_add(1))); + } + } +} + +/// Boxes of zero bytes: every part zero bytes are a value of reads as that value, an +/// enum as the variant zero bytes are, and a part they are no value of, such as a +/// reference or a `String`, is written before it is read; a box never taken by +/// `assume_init` drops nothing. +pub fn boxed_zeroed(data: &[u8], out: &mut Vec) { + let first = data.first().copied().unwrap_or(0); + out.extend_from_slice(&unsafe { Box::::new_zeroed().assume_init() }.to_le_bytes()); + out.extend_from_slice(&unsafe { Box::::new_zeroed().assume_init() }.to_le_bytes()); + out.extend_from_slice(&unsafe { Box::::new_zeroed().assume_init() }.to_le_bytes()); + out.extend_from_slice(&unsafe { Box::::new_zeroed().assume_init() }.to_le_bytes()); + out.push(u8::from(*unsafe { Box::::new_zeroed().assume_init() })); + out.push(*unsafe { Box::::new_zeroed().assume_init() } as u8); + out.push(u8::from( + unsafe { Box::<*const u8>::new_zeroed().assume_init() }.is_null(), + )); + + // Enums, as the variant zero bytes are: a tag of zero, or a niche that holds one. + out.extend([ + u8::from(unsafe { Box::>::new_zeroed().assume_init() }.is_none()), + u8::from(unsafe { *Box::>::new_zeroed().assume_init() } == Some(false)), + u8::from(unsafe { Box::>::new_zeroed().assume_init() }.is_none()), + u8::from(unsafe { Box::>::new_zeroed().assume_init() }.is_none()), + u8::from(unsafe { Box::>>::new_zeroed().assume_init() }.is_none()), + u8::from(*unsafe { Box::::new_zeroed().assume_init() } == std::cmp::Ordering::Equal), + u8::from(*unsafe { Box::::new_zeroed().assume_init() } != Tag::One), + u8::from(*unsafe { Box::>::new_zeroed().assume_init() } == Some(Tag::Zero)), + ]); + + // Std's types that zero bytes are a value of. + let cell = unsafe { Box::>::new_zeroed().assume_init() }; + cell.set(cell.get() + u16::from(first)); + out.extend_from_slice(&cell.get().to_le_bytes()); + let atomic = unsafe { Box::::new_zeroed().assume_init() }; + atomic.fetch_add(u32::from(first) << 8, std::sync::atomic::Ordering::Relaxed); + out.extend_from_slice(&atomic.load(std::sync::atomic::Ordering::Relaxed).to_le_bytes()); + let wrapped = unsafe { *Box::>::new_zeroed().assume_init() } - std::num::Wrapping(first); + out.push(wrapped.0); + let elapsed = unsafe { *Box::::new_zeroed().assume_init() }; + out.extend_from_slice( + &(elapsed + std::time::Duration::from_millis(u64::from(first))) + .as_micros() + .to_le_bytes(), + ); + + let shared = unsafe { Box::>::new_zeroed().assume_init() }; + *shared.borrow_mut() += u16::from(first); + out.extend_from_slice(&shared.borrow().to_le_bytes()); + let locked = unsafe { Box::>::new_zeroed().assume_init() }; + *locked.lock().unwrap() ^= first; + out.push(*locked.lock().unwrap()); + + // Std's wrappers of a reference, which zero bytes are no value of, written in part. + let mut wrapped = Box::>::new_zeroed(); + unsafe { (&raw mut (*wrapped.as_mut_ptr()).0.1).write(&first) }; + let std::num::Wrapping((n, lent)) = *unsafe { wrapped.assume_init() }; + out.extend_from_slice(&n.to_le_bytes()); + out.push(*lent); + let mut cell = Box::>::new_zeroed(); + unsafe { (&raw mut (*(*cell.as_mut_ptr()).get()).1).write(&first) }; + let (n, lent) = unsafe { cell.assume_init() }.into_inner(); + out.extend_from_slice(&n.to_le_bytes()); + out.push(*lent); + + // One whose variant of zero bytes holds a reference, which a whole write replaces. + for value in [Held::Lent(&first, 7), Held::Count(u32::from(first) + 1)] { + let mut held = Box::::new_zeroed(); + held.write(value); + match *unsafe { held.assume_init() } { + Held::Lent(b, n) => out.extend([*b, n as u8]), + Held::Count(n) => out.push(n as u8), + } + } + + // Arrays and tuples, written in part before they are read. + let mut bytes = Box::<[u8; 16]>::new_zeroed(); + unsafe { bytes.as_mut_ptr().cast::().add(3).write(first) }; + out.extend_from_slice(&*unsafe { bytes.assume_init() }); + let mut grid = Box::<[[u16; 2]; 3]>::new_zeroed(); + unsafe { (*grid.as_mut_ptr())[1][0] = u16::from(first) + 1 }; + for row in *unsafe { grid.assume_init() } { + out.extend(row.iter().flat_map(|w| w.to_le_bytes())); + } + let (a, b, c) = *unsafe { Box::<(u8, i32, bool)>::new_zeroed().assume_init() }; + out.extend([a, b as u8, u8::from(c)]); + + // A reference, which zero bytes are no value of, written before it is read. + let mut lent = Box::<(u32, &u8)>::new_zeroed(); + unsafe { (&raw mut (*lent.as_mut_ptr()).1).write(&first) }; + let lent = unsafe { lent.assume_init() }; + out.extend_from_slice(&lent.0.to_le_bytes()); + out.push(*lent.1); + let mut named = Box::<(String, u16)>::new_zeroed(); + unsafe { (&raw mut (*named.as_mut_ptr()).0).write(format!("{first}")) }; + let named = unsafe { named.assume_init() }; + out.extend_from_slice(named.0.as_bytes()); + out.extend_from_slice(&named.1.to_le_bytes()); + + // The crate's struct, whole, then with the value its drop counts through written in. + let counts = std::cell::Cell::new((0, 0)); + let zeroed = unsafe { Box::::new_zeroed().assume_init() }; + out.extend_from_slice(&zeroed.n.to_le_bytes()); + out.extend([ + u8::from(zeroed.on), + zeroed.c as u8, + zeroed.f as u8, + zeroed.wide as u8, + zeroed.signed as u8, + ]); + out.extend(zeroed.words.iter().flat_map(|w| w.to_le_bytes())); + out.push(zeroed.many.iter().fold(0, |n, &w| n | w) as u8); + out.extend([zeroed.pair.0, zeroed.pair.1 as u8, zeroed.tag as u8]); + out.extend([u8::from(zeroed.maybe == Some(Tag::Zero)), u8::from(zeroed.p.is_null())]); + drop(zeroed); + let mut seen = Box::::new_zeroed(); + unsafe { + let p = seen.as_mut_ptr(); + (&raw mut (*p).seen).write(Some(&counts)); + (&raw mut (*p).many[299]).write(u32::from(first)); + } + let seen = unsafe { seen.assume_init() }; + out.push(seen.many[299] as u8); + drop(seen); + out.push(counts.get().1); + let mut kept = Box::::new_zeroed(); + unsafe { (&raw mut (*kept.as_mut_ptr()).seen).write(Some(&counts)) }; + drop(kept); + out.push(counts.get().1); + + // Made by a generic function. + out.extend_from_slice(&zeroed_box::().to_le_bytes()); + let (x, y) = *zeroed_box::<(u8, u16)>(); + out.extend([x, y as u8]); +} + +/// A box of zero bytes, taken as a `T`. +fn zeroed_box() -> Box { + unsafe { Box::new_zeroed().assume_init() } +} diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 306620653..c9dfece98 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -2469,17 +2469,10 @@ fn mapping<'tcx>( let size = super::iter::size_of(b, elem, span); Expr::rs("sliceNewUninit", vec![len, size, each]) } - // The slice, which JS holds as what its `MaybeUninit`s hold. - ("Box", "assume_init") - if tcx - .type_of(tcx.parent(call.def_id)) - .instantiate_identity() - .skip_norm_wip() - .boxed_ty() - .is_some_and(|t| t.is_slice()) => - { - args.pop()? - } + // One value not yet written, as `MaybeUninit::uninit` makes it. + ("Box", "new_uninit") => b.t.uninit(Ty::new_maybe_uninit(tcx, targ(gargs, 0)), span), + // What the box holds, which JS holds as what its `MaybeUninit` holds. + ("Box", "assume_init") => args.pop()?, // `vec![x; n]`: `n - 1` clones of `x`, then `x` itself, which the caller gives as its // own, as it does whatever std keeps, or `x` dropped where `n` is 0. ("vec", "from_elem") => { diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 4da8a6602..3a9478051 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -540,6 +540,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { "replace" => done(self.operand(&args[1].node, span, out), false), _ => done(Expr::undefined(), false), }, + ("Box", "new_zeroed") => done(self.zeroed(targ(0)?, span), false), // Replacing what a `&mut` borrows. // What `Default::default` makes before the swap. ("mem", "take") => { diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 7de8a4a69..c435876a8 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -8,9 +8,10 @@ use rustc_abi::{BackendRepr, FieldIdx, Size, VariantIdx}; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIndex}; -use rustc_middle::mir::interpret::{AllocId, ConstAllocation, GlobalAlloc, GlobalId, Scalar, alloc_range}; +use rustc_middle::mir::interpret::{AllocId, Allocation, ConstAllocation, GlobalAlloc, GlobalId, Scalar, alloc_range}; use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::{self, ConstValue, Local, Operand, Place, PlaceTy, ProjectionElem}; +use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::Span; @@ -1574,6 +1575,115 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } } + /// A `MaybeUninit` of zero bytes, as `Box::new_zeroed` makes it: each part that zero + /// bytes are a value of holds that value, and the rest, such as a reference, holds + /// nothing yet, as [`Translator::uninit`] makes it, for a write to give it one before a + /// read. It is cast as that is, so `tsc` narrows it to no variant a write may change. + pub(super) fn zeroed(&mut self, ty: Ty<'tcx>, span: Span) -> Expr { + let value = self.zero(ty, span); + let ts_ty = self.b.t.ty(Ty::new_maybe_uninit(self.tcx, ty), span); + Expr::As( + Box::new(Expr::As(Box::new(value), crate::ts::Type::Builtin("unknown"))), + ts_ty, + ) + } + + /// What [`Self::zeroed`] holds, untyped. + fn zero(&mut self, ty: Ty<'tcx>, span: Span) -> Expr { + let tcx = self.tcx; + let ty = self.b.t.uninit_inner(ty); + match ty.kind() { + ty::Array(elem, len) => { + let len = Expr::lit(len.try_to_target_usize(tcx).unwrap_or(0).to_string()); + if crate::lower::byte(*elem) { + return Expr::New(Box::new(Expr::ident("Uint8Array")), vec![len]); + } + let elem_ty = self.b.t.ty(*elem, span); + let each = self.zero(*elem, span); + let each = Expr::Arrow(Vec::new(), elem_ty, vec![StmtKind::Return(Some(each)).at(None)]); + let length = Expr::Object(vec![(crate::ts::Key::Name("length".into()), len)]); + Expr::ident("Array").member("from").call(vec![length, each]) + } + ty::Tuple(tys) if !tys.is_empty() => { + let values = tys.iter().map(|t| self.zero(t, span)).collect(); + let ts_ty = self.b.t.ty(ty, span); + Expr::As(Box::new(Expr::Array(values)), ts_ty) + } + // A cell holds its value as a part of it, which a pointer from `as_ptr` reaches; zero + // bytes are one unborrowed, unlocked, and unpoisoned. + ty::Adt(adt, args) + if let Some(class) = match Std::of(tcx, adt.did()) { + Some(Std::Cell) => Some("Cell"), + Some(Std::RefCell) => Some("RefCell"), + Some(Std::Mutex) => Some("Mutex"), + _ => None, + } => + { + let inner = self.zero(args.type_at(0), span); + Expr::New(Box::new(Expr::Rs(class.into())), vec![inner]) + } + // The crate's struct, or one of std's JS keeps as the value it wraps. + ty::Adt(adt, args) + if adt.is_struct() + && (self.b.t.own(adt.did()) + || matches!( + Std::of(tcx, adt.did()), + Some(Std::Reverse | Std::AssertUnwindSafe | Std::Wrapping | Std::Saturating) + )) + && adt.all_fields().next().is_some() => + { + let values = (adt.non_enum_variant().fields.iter()) + .map(|f| { + let t = self.b.t.field_ty(f.did, args); + self.zero(t, span) + }) + .collect(); + self.adt(adt.did(), rustc_abi::FIRST_VARIANT, args, values, ty, span) + } + _ => { + let env = self.b.t.typing_env_pub(); + let valid = tcx.check_validity_requirement((ValidityRequirement::Zero, env.as_query_input(ty))); + let Ok(layout) = tcx.layout_of(env.as_query_input(ty)) else { + return self.err(span, format!("zero bytes of `{ty}`, whose layout is unknown")); + }; + if !matches!(valid, Ok(true)) { + return self.b.t.uninit(ty, span); + } + let memory = Allocation::from_bytes( + vec![0; layout.size.bytes_usize()], + layout.align.abi, + rustc_ast::Mutability::Not, + (), + ); + let val = ConstValue::Indirect { + alloc_id: tcx.reserve_and_set_memory_alloc(tcx.mk_const_alloc(memory)), + offset: Size::ZERO, + }; + // An enum is the variant its tag or niche of zero picks, whose fields rustc's + // check of the tag alone doesn't check, so each is zeroed as a part. + let ty::Adt(adt, args) = ty.kind() else { + return self.const_value(val, ty, span); + }; + if !adt.is_enum() { + return self.const_value(val, ty, span); + } + let Some(variant) = tcx + .try_destructure_mir_constant_for_user_output(val, ty) + .and_then(|d| d.variant) + else { + return self.err(span, format!("zero bytes of `{ty}`, which are none of its variants")); + }; + let values = (adt.variant(variant).fields.iter()) + .map(|f| { + let t = self.b.t.field_ty(f.did, args); + self.zero(t, span) + }) + .collect(); + self.adt(adt.did(), variant, args, values, ty, span) + } + } + } + /// The value of a constant `val` of type `ty`: a fresh one at each use, as Rust copies a /// constant there. pub(super) fn const_value(&mut self, val: ConstValue, ty: Ty<'tcx>, span: Span) -> Expr { @@ -1616,6 +1726,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { _ => unreachable!(), }; } + if let ty::Pat(base, _) = ty.kind() { + return self.const_value(val, *base, span); + } match val { ConstValue::ZeroSized => match *ty.kind() { ty::FnDef(def_id, args) => self.b.fn_value(def_id, args.skip_binder(), span), diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 2fc4f3291..e0ec32667 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -848,6 +848,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { crate::lower::task::raw_waker(data, vtable) } (Std::LayoutError, _) => Expr::Rs("LAYOUT_ERROR".into()), + (Std::Duration, _) => { + let [secs, nanos] = <[Expr; 2]>::try_from(values).expect("a duration's two fields"); + crate::lower::time::object(secs, nanos) + } (Std::ParseIntError, _) => Expr::Object(vec![(Key::Name("kind".into()), values.pop().unwrap())]), (Std::ErrorKind, _) => Expr::As(Box::new(Expr::Str(def.name.to_string())), self.b.t.ty(ty, span)), (Std::Ordering, _) => crate::lower::collections::ordering_variant(variant), diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index dc6ef7636..2cc5a351e 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -335,7 +335,7 @@ pub fn derives(tcx: ty::TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> Option< ("ptr", "cast" | "cast_const" | "cast_mut" | "as_ref" | "as_mut") | ("NonNull", "new" | "new_unchecked" | "as_ptr" | "as_ref" | "as_mut" | "cast" | "cast_const" | "cast_mut") | ("MaybeUninit", "as_ptr" | "as_mut_ptr" | "assume_init_ref" | "assume_init_mut" | "write") - | ("Box", "into_raw" | "leak" | "from_raw") => Some(Derives::Same), + | ("Box", "into_raw" | "leak" | "from_raw" | "write") => Some(Derives::Same), ( "ptr", "null" | "null_mut" | "dangling" | "dangling_mut" | "is_null" | "eq" | "add" | "sub" | "offset" @@ -360,7 +360,7 @@ pub fn derives(tcx: ty::TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> Option< pub fn stores(tcx: ty::TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> bool { matches!( super::library::key(tcx, def_id), - Some((owner, name)) if matches!(owner.as_str(), "ptr" | "NonNull" | "MaybeUninit") + Some((owner, name)) if matches!(owner.as_str(), "ptr" | "NonNull" | "MaybeUninit" | "Box") && matches!(name.as_str(), "write" | "write_unaligned" | "write_volatile") ) } @@ -812,6 +812,24 @@ pub fn call<'tcx>( out.push(StmtKind::Expr(write).at(None)); p } + // The box, written into as `MaybeUninit::write` writes, so a pointer into it from + // before still points into what it holds; one of a value JS copies is that value. + ("Box", "write") => { + let boxed = arg(); + let value = arg(); + match kind(b, pointee()) { + Some(Pointee::Place) => value, + _ => { + let p = b.spill(boxed, "uninit", out); + let write = match array(b, pointee()) { + true => Expr::rs("ptrWrite", vec![p.clone(), value]), + false => write(b, p.clone(), value, pointee(), span), + }; + out.push(StmtKind::Expr(write).at(None)); + p + } + } + } // A shared reference to a value JS copies is the value. ("MaybeUninit", "assume_init_read") => b.t.snapshot(arg(), pointee(), span), ("MaybeUninit", "assume_init_drop") | ("ManuallyDrop", "drop") => { diff --git a/ts/src/lower/time.rs b/ts/src/lower/time.rs index 3f9010f5d..d8c383251 100644 --- a/ts/src/lower/time.rs +++ b/ts/src/lower/time.rs @@ -104,7 +104,8 @@ pub fn constant<'tcx>(fields: &[(Ty<'tcx>, ValTree<'tcx>)]) -> Option { Some(object(U64.lit(i128::from(secs)), Expr::lit(nanos.to_string()))) } -fn object(secs: Expr, nanos: Expr) -> Expr { +/// A `Duration` of `secs` whole seconds and `nanos` nanoseconds. +pub fn object(secs: Expr, nanos: Expr) -> Expr { Expr::Object(vec![ (Key::Name("secs".into()), secs), (Key::Name("nanos".into()), nanos),