diff --git a/quest/m3/README.md b/quest/m3/README.md index cbaa3685f..f451cc0fd 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -48,6 +48,5 @@ memory model for values whose address is reinterpreted, without a consumer. - [Parts of arrays without arrayView](/quest/m3/ts-slice-object.md) - a `&mut [T; N]` to part of an array is taken where it is, with no `Proxy` - [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md) - no runtime `rust2-ts:` error where native Rust goes on: an exact slower path, or a refusal at translation - [rust2-ts with no ts.toml](/quest/m3/ts-configless.md) - a crate's whole `pub` API translates with no config and no bound items -- [A slice of one byte's place](/quest/m3/ts-byte-slice-place.md) - `slice::from_mut` of a `&mut u8` writes the byte where the caller holds it - [Browser APIs through wasm-bindgen](/quest/m3/ts-wasm-bindgen/README.md) - web-sys and js-sys imports become direct JS calls, so idiomatic crates such as web-rs's web-codecs translate as stock crates - [Fields typed by an associated type](/quest/m3/ts-assoc-type-fields.md) - a generic struct's field of `::Item` translates at each instance diff --git a/quest/m3/ts-byte-slice-place.md b/quest/m3/ts-byte-slice-place.md deleted file mode 100644 index 8fccdc4a7..000000000 --- a/quest/m3/ts-byte-slice-place.md +++ /dev/null @@ -1,24 +0,0 @@ -# [S] A slice of one byte's place - -## Goal - -[rust2-ts](/ts/README.md) translates `slice::from_mut` and `array::from_mut` of a -`&mut u8`, of a local, a field, or an element of an array of bytes alike, which it refuses -today: the slice writes the byte where the caller holds it. Proven by differential tests -against native Rust. - -## Plan - -✅ Decided with the maintainer on 2026-11-06 in -[moq-dev/rust2#608](https://github.com/moq-dev/rust2/pull/608): keep it, deferred to this -quest of its own, as rust2 aims to support all of std. The options were to drop it until -a consumer appears, since moq has none at its pin, or to keep it and measure the -`rs.Place`-backed `rs.ByteSlice` below (picked). - -A slice of one over a `&mut u8` was found fragile when it meant a one-byte buffer for each -local; an `rs.ByteSlice` whose storage can be an `rs.Place` may avoid that. Measure what -each form costs a hot `&mut u8`, such as `read_exact(slice::from_mut(&mut byte))`, against -the `rs.ByteSlice` over a `Uint8Array` every other byte slice is. - -Public API: the translator's output, and what a hand-written caller may receive as a -`rs.ByteSlice`. diff --git a/quest/m3/ts-slice-object.md b/quest/m3/ts-slice-object.md index 8b0a4f81f..2c9188be3 100644 --- a/quest/m3/ts-slice-object.md +++ b/quest/m3/ts-slice-object.md @@ -23,8 +23,8 @@ go. Proven by differential tests against native Rust. - (b) Bytes only: `as_chunks_mut` makes eager `subarray` views, keeping the `Proxy` path for other elements. - (c) Keep the `Proxy` views and drop the item. -2. `slice::from_mut` of a `&mut u8`: kept, deferred to - [a quest of its own](/quest/m3/ts-byte-slice-place.md). +2. `slice::from_mut` of a `&mut u8`: kept, deferred to a quest of its own, since done + ([moq-dev/rust2#660](https://github.com/moq-dev/rust2/pull/660)). 3. Comparing `*const [T]` of values JS copies: moved to [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md). diff --git a/ts/README.md b/ts/README.md index 191b3f964..dfb3a7e0b 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: its items, the array `items`, and the capacity std's holds; `rs.vecFrom(items)` makes one of an array with room for exactly them. `rs.ByteVec` of bytes | | `Cow<[T]>`, `Cow` | `rs.Cow`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows, `rs.Cow>` holding the arrays of a slice of arrays; `rs.ByteCow` for bytes, an `rs.ByteVec` or an `rs.ByteSlice`; `rs.StrCow` for text, the `rs.String` it owns or an `rs.Borrowed` holding the string it borrows, whose text `rs.strCowStr` reads | | `&T`, `&mut T`, `Box`, `Reverse`, `NonZero`, `AssertUnwindSafe` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rs.Place`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust, but a byte variable whose `&raw` the body takes, or that of a reference to it made there, as `&mut b as *mut u8` does, is element 0 of a `Uint8Array` of its own, which its raw, mutable, and shared slices view as they do any other bytes; JS keeps an array of one, and a shared subslice of a byte in no array, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, and `offset_from` between pointers the body shows JS keeps in different arrays, are refused, and through a pointer from elsewhere any of these, a raw or mutable slice of a byte in no array, such as a field's, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. A pointer to a slice cast to one to an array of its elements is the `rs.Slice` or `rs.ByteSlice` of that many from its first, read through only where Rust reads. A `*const ()` is an identity, such as a waker's data, and `rs.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rs.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rs.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rs` class such as `rs.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | +| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust, but a byte variable whose `&raw` the body takes, or that of a reference to it made there, as `&mut b as *mut u8` does, or of whose `&mut` it makes a slice of one, is element 0 of a `Uint8Array` of its own, which its raw, mutable, and shared slices view as they do any other bytes; JS keeps an array of one, and a shared subslice of a byte in no array, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, and `offset_from` between pointers the body shows JS keeps in different arrays, are refused, and through a pointer from elsewhere any of these, a raw or mutable slice of a byte in no array, such as a field's, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. A pointer to a slice cast to one to an array of its elements is the `rs.Slice` or `rs.ByteSlice` of that many from its first, read through only where Rust reads. 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 `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 | @@ -168,7 +168,15 @@ bytes. A struct's private `&[u8]` or `&[u8; N]` field is its three parts, `bufDa `bufStart`, and `bufLen` for `buf`, so a cursor moving through it makes no object; a read that needs the slice whole makes it there. A caller passes bytes as `rs.byteSliceFrom(array)` and reads a slice it gets as `rs.byteSliceView(slice)`, a view -of it. An `rs.Slice` is the same of other values, a `&[T]`, `&mut [T]`, or `Box<[T]>`: +of it. `slice::from_mut`, `array::from_mut`, and `Option::as_mut_slice` of a byte view it +where the body shows it is in a `Uint8Array`: an element of an array, slice, or `Vec` of +bytes, or a byte variable, which one of its own then holds. Any other byte, such as a +field's or one a parameter points at, they copy into an `rs.ByteMirror`, and the function +writes the copy back after each statement or call that may write through the slice, +whenever the byte changed, as Rust reaches the byte only through the slice while it is in +use. A slice of a copy kept where no variable of the function holds it, such as in what +it returns or in memory a parameter points at, or that may still be used once its call +makes another, is refused. An `rs.Slice` is the same of other values, a `&[T]`, `&mut [T]`, or `Box<[T]>`: its `data`, a JS array, from `start`, for `length` elements, read and written where they are, so `&mut v[a..b]`, `split_at_mut`, `chunks_mut`, and `split_mut` lend parts of the same array. A caller passes one as `rs.sliceFrom(array)` and reads one it gets as diff --git a/ts/fixtures/lang/conversions/expected/conversions.Cursor.ts b/ts/fixtures/lang/conversions/expected/conversions.Cursor.ts new file mode 100644 index 000000000..d77b6198e --- /dev/null +++ b/ts/fixtures/lang/conversions/expected/conversions.Cursor.ts @@ -0,0 +1,31 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** Reads bytes off the front of a slice, as a decoder does. */ +export interface Cursor { + dataData: Uint8Array; + dataStart: number; + dataLen: number; +} + +/** Fills `buf` from the front, or leaves it as it is if too few are left. */ +export function readExact(self: Cursor, buf: rs.ByteSlice): boolean { + if (!(self.dataLen < buf.length)) { + const _14Data: Uint8Array = self.dataData; + const _14Start: number = self.dataStart; + const _14Len: number = self.dataLen; + const mid: number = buf.length; + if (mid > _14Len) { + rs.panic("mid > len"); + } + rs.byteSliceCopyFromSlice(buf, new rs.ByteSlice(_14Data, _14Start, mid)); + self.dataData = _14Data; + self.dataStart = _14Start + mid; + self.dataLen = _14Len - mid; + return true; + } + return false; +} + +//# sourceMappingURL=conversions.Cursor.ts.map diff --git a/ts/fixtures/lang/conversions/expected/conversions.Header.ts b/ts/fixtures/lang/conversions/expected/conversions.Header.ts new file mode 100644 index 000000000..ef06926f2 --- /dev/null +++ b/ts/fixtures/lang/conversions/expected/conversions.Header.ts @@ -0,0 +1,45 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { mapOr$0vxw5sq as option_mapOr$0vxw5sq } from "./core/option.ts"; + +/** + * Bytes in each place a `&mut u8` reaches: fields, a tuple's, an array's, an `Option`'s, + * and a `MaybeUninit`'s. + */ +export interface Header { + tag: rs.u8; + pair: [rs.u8, rs.u8]; + bytes: Uint8Array; + payload: rs.u8 | undefined; + uninit: rs.u8; +} + +/** Each byte, the `MaybeUninit`'s once written. */ +export function put(self: Header, out: rs.ByteVec): void { + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(self.tag, self.pair[0], self.pair[1], self.bytes[0], self.bytes[1])), undefined, true); + rs.byteVecPush(out, option_mapOr$0vxw5sq(self.payload, 238, (p: rs.u8): rs.u8 => { + return p ^ 128; + })); +} + +/** Adds `by` to its tag through a slice of one of it. */ +export function bump(self: Header, by: rs.u8): void { + let mirror!: rs.ByteMirror | undefined; + const _4: rs.ByteSlice = (mirror = rs.byteMirror(rs.place(() => self.tag, (value) => self.tag = value))).s; + const iterS: Uint8Array = _4.data; + let iterI: number = _4.start; + const iterE: number = _4.start + _4.length; + rs.byteMirrorFlush(mirror); + while (true) { + const _7: number | rs.Done = iterI < iterE ? iterI++ : rs.DONE; + rs.byteMirrorFlush(mirror); + if (_7 === rs.DONE) { + break; + } + iterS[_7] = rs.u8WrappingAdd(iterS[_7], by); + rs.byteMirrorFlush(mirror); + } +} + +//# sourceMappingURL=conversions.Header.ts.map diff --git a/ts/fixtures/lang/conversions/expected/conversions.ts b/ts/fixtures/lang/conversions/expected/conversions.ts index f28c6014f..14bbc791f 100644 --- a/ts/fixtures/lang/conversions/expected/conversions.ts +++ b/ts/fixtures/lang/conversions/expected/conversions.ts @@ -1,12 +1,14 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$array_Option_u16, copy$array_array0_u8, copy$array_array1_u8, copy$array_array2_u32, copy$array_array3_u8, copy$array_array40_u8, copy$array_u32, copy$array_u64, copy$array_u8, copy$conversions_Logged, copy$conversions_Named, drop$tuple2_conversions_Logged_u8 } from "./lib.ts"; +import { copy$array_Option_u16, copy$array_array0_u8, copy$array_array1_u8, copy$array_array2_u32, copy$array_array3_u8, copy$array_array40_u8, copy$array_u32, copy$array_u64, copy$array_u8, copy$conversions_Logged, copy$conversions_Named, drop$tuple2_conversions_Logged_u8, out$u8 } from "./lib.ts"; +import * as Cursor from "./conversions.Cursor.ts"; +import * as Header from "./conversions.Header.ts"; import * as Held from "./conversions.Held.ts"; import * as Kept from "./conversions.Kept.ts"; import * as Logged from "./conversions.Logged.ts"; import * as Named from "./conversions.Named.ts"; -import { thenSome$1gvhopg } from "./core/bool.ts"; +import { thenSome$1gvhopg, then$1ldvmdq } from "./core/bool.ts"; import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, mapOr$14runhi as option_mapOr$14runhi, clone$0wpatp7 as option_clone$0wpatp7, clone$13oewp0 as option_clone$13oewp0, clone$0xr8mw0 as option_clone$0xr8mw0, clone$17fv6u9 as option_clone$17fv6u9, clone$0fp7wpk as option_clone$0fp7wpk, clone$1lfse5o as option_clone$1lfse5o, clone$08wmfj8 as option_clone$08wmfj8, clone$1aebjgp as option_clone$1aebjgp } from "./core/option.ts"; /** The byte at `at`, past `data`'s end its index. */ @@ -175,9 +177,8 @@ export function LOG$init(_1: undefined): rs.Arc> { } /** - * Runs each conversion of the type `kind` picks, but a slice of one `u8`, whose `&mut` - * no slice can view, and [`searched`] of those that order; and [`referenced`] of one - * type of each form JS keeps a value in. + * Runs each conversion of the type `kind` picks, and [`searched`] of those that order; + * and [`referenced`] of one type of each form JS keeps a value in. */ export function conversions(kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): void { const _5: rs.u8 = rs.u8Rem(kind, 14); @@ -213,8 +214,7 @@ export function conversions(kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): v ordered$Vec_u8(data, out); referenced$Vec_u8(data, out); } else if (_5 === 12) { - converted$u8(data, out); - searched$u8(data, out); + ordered$u8(data, out); referenced$u8(data, out); } else { ((log: rs.Arc>): undefined => { @@ -330,6 +330,14 @@ export function ordered$u64(data: rs.ByteSlice, out: rs.ByteVec): void { searched$u64(data, out); } +/** Each conversion of a `T` that orders. */ +export function ordered$u8(data: rs.ByteSlice, out: rs.ByteVec): void { + converted$u8(data, out); + lentOne$u8(data, out); + lentThrough$u8(data, out); + searched$u8(data, out); +} + /** `clone_from` through a `&mut` whose place only the caller knows. */ export function cloneFromParam$Option_u16(to: rs.Place, from: rs.u16 | undefined): void { to.v = from; @@ -637,6 +645,19 @@ export function writeFirst$u64(s: rs.Slice, value: rs.u64): rs.u64 { return old; } +/** A whole write into what a slice holds, as a function given any slice makes it. */ +export function writeFirst$u8(s: rs.ByteSlice, value: rs.u8): rs.u8 { + rs.idx(s, 0); + const _8: rs.u8 = value; + const old2 = s.data[s.start]; + s.data[s.start] = _8; + const old: rs.u8 = old2; + const _10: rs.u8 = value; + rs.idx(s, 0); + s.data[s.start] = _10; + return old; +} + /** `mem::replace` of the first array, as a function given any slice of them makes it. */ export function replaceFirst$Option_u16(s: rs.ArraySlice>, value: Array): Array { rs.idx(s, 0); @@ -715,6 +736,15 @@ export function replaceFirst$u64(s: rs.ArraySlice>, value: rs.u return rs.replaceIn(rs.sliceView(rs.arraySliceAt(s, 0)), copy$array_u64(value)); } +/** `mem::replace` of the first array, as a function given any slice of them makes it. */ +export function replaceFirst$u8(s: rs.ArraySlice, value: Uint8Array): Uint8Array { + rs.idx(s, 0); + const slice = rs.arraySliceAt(s, 0); + const old = rs.byteSliceToArray(slice); + slice.data.set(copy$array_u8(value), slice.start); + return old; +} + /** * A slice of what a `&mut` whose place only the caller knows points at: written whole, * read out by `mem::replace` and by `mem::swap` either way round, with a local, with @@ -1675,6 +1705,121 @@ export function lentParam$u64(to: rs.Place, data: rs.ByteSlice, out: rs. put$conversions_Value_u64(s.data[s.start], out); } +/** + * A slice of what a `&mut` whose place only the caller knows points at: written whole, + * read out by `mem::replace` and by `mem::swap` either way round, with a local, with + * another slice's element, as an array, of a reference to it, by a cell, and nested in + * arrays, and written in place. + */ +export function lentParam$u8(to: rs.Place, data: rs.ByteSlice, out: rs.ByteVec): void { + let mirror!: rs.ByteMirror | undefined; + const s: rs.ByteSlice = (mirror = rs.byteMirror(to)).s; + const _7: rs.u8 = writeFirst$u8(s, make$conversions_Value_u8(data, 18)); + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(_7, out); + let other: rs.u8 = make$conversions_Value_u8(data, 19); + rs.idx(s, 0); + const swapped = s.data[s.start]; + s.data[s.start] = other; + other = swapped; + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(other, out); + rs.idx(s, 0); + const swapped2 = other; + other = s.data[s.start]; + s.data[s.start] = swapped2; + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(other, out); + const t: rs.ByteSlice = rs.byteSliceRange(rs.byteVecSlice(rs.byteVecFrom(Uint8Array.of(make$conversions_Value_u8(data, 20), make$conversions_Value_u8(data, 21)))), 0); + rs.idx(s, 0); + rs.idx(t, 1); + const swapped3 = s.data[s.start]; + s.data[s.start] = t.data[t.start + 1]; + t.data[t.start + 1] = swapped3; + rs.byteMirrorFlush(mirror); + rs.idx(t, 0); + rs.idx(s, 0); + const swapped4 = t.data[t.start]; + t.data[t.start] = s.data[s.start]; + s.data[s.start] = swapped4; + rs.byteMirrorFlush(mirror); + rs.idx(t, 0); + put$conversions_Value_u8(t.data[t.start], out); + rs.idx(t, 1); + put$conversions_Value_u8(t.data[t.start + 1], out); + rs.idx(s, 0); + const a: rs.ByteSlice = new rs.ByteSlice(s.data, s.start, 1); + rs.byteMirrorFlush(mirror); + const _77: Uint8Array = Uint8Array.of(make$conversions_Value_u8(data, 22)); + const old4 = rs.byteSliceToArray(a); + a.data.set(_77, a.start); + const _76: Uint8Array = old4; + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(_76[0], out); + let by: rs.u8 = make$conversions_Value_u8(data, 23); + rs.idx(s, 0); + const borrowed = s.start; + let r: rs.Place = rs.place(() => s.data[borrowed], (value) => s.data[borrowed] = value); + const _89: rs.Slice> = rs.sliceOfPlace(rs.place(() => r, (value2) => r = value2)); + rs.byteMirrorFlush(mirror); + const _91: rs.Place[] = [rs.place(() => by, (value3) => by = value3)]; + const _88: rs.Place[] = rs.replaceIn(rs.sliceView(_89), _91); + rs.byteMirrorFlush(mirror); + const _93: rs.u8 = make$conversions_Value_u8(data, 24); + rs.byteMirrorFlush(mirror); + _88[0].v = _93; + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(r.v, out); + rs.byteMirrorFlush(mirror); + rs.idx(s, 0); + put$conversions_Value_u8(s.data[s.start], out); + rs.byteMirrorFlush(mirror); + rs.idx(s, 0); + const borrowed2 = s.data; + const borrowed3 = s.start; + const _105: rs.Cell = rs.cellOfPlace(rs.place(() => borrowed2[borrowed3], (value4) => borrowed2[borrowed3] = value4)); + rs.byteMirrorFlush(mirror); + const _104: rs.u8 = rs.cellReplace(_105, make$conversions_Value_u8(data, 25)); + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(_104, out); + rs.idx(s, 0); + const _115: rs.ByteSlice = new rs.ByteSlice(s.data, s.start, 1); + rs.byteMirrorFlush(mirror); + const _114: rs.Cell = rs.cellOfPlace(rs.partPlace(rs.byteSliceView(_115))); + rs.byteMirrorFlush(mirror); + const _121: Uint8Array = Uint8Array.of(make$conversions_Value_u8(data, 26)); + const pointer = rs.byteSliceFrom(_114.v); + const old6 = rs.byteSliceToArray(pointer); + rs.byteSliceSet(pointer, _121); + const _113: Uint8Array = old6; + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(_113[0], out); + rs.idx(s, 0); + const a2: rs.ByteSlice = new rs.ByteSlice(s.data, s.start, 1); + rs.byteMirrorFlush(mirror); + const _133: rs.ArraySlice = rs.arraySliceOne(a2, 1); + rs.byteMirrorFlush(mirror); + const _132: Uint8Array = replaceFirst$u8(_133, Uint8Array.of(make$conversions_Value_u8(data, 27))); + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(_132[0], out); + rs.idx(s, 0); + const _139: rs.ByteSlice = new rs.ByteSlice(s.data, s.start, 1); + rs.byteMirrorFlush(mirror); + const b: rs.ArraySlice = rs.arraySliceOne(_139, 1); + rs.byteMirrorFlush(mirror); + const _147: rs.Cell = rs.cellOfArraySlice(b); + rs.byteMirrorFlush(mirror); + const _146: Uint8Array[] = rs.cellReplace(_147, [Uint8Array.of(make$conversions_Value_u8(data, 28))]); + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(_146[0][0], out); + rs.idx(s, 0); + s.data[s.start] = other; + rs.byteMirrorFlush(mirror); + rs.idx(s, 0); + put$conversions_Value_u8(s.data[s.start], out); + rs.byteMirrorFlush(mirror); +} + /** * [`lent_param`] of a local, a field, an element, and through a `&mut` to a `&mut`, each * read after. @@ -1931,6 +2076,27 @@ export function lentThrough$u64(data: rs.ByteSlice, out: rs.ByteVec): void { put$conversions_Value_u64(v.items[rs.idx(v.items, 1)], out); } +/** + * [`lent_param`] of a local, a field, an element, and through a `&mut` to a `&mut`, each + * read after. + */ +export function lentThrough$u8(data: rs.ByteSlice, out: rs.ByteVec): void { + let a: rs.u8 = make$conversions_Value_u8(data, 0); + const held: Held.Held = new Held.Held(make$conversions_Value_u8(data, 1)); + const v: rs.ByteVec = rs.byteVecFrom(Uint8Array.of(make$conversions_Value_u8(data, 2), make$conversions_Value_u8(data, 4))); + lentParam$u8(rs.place(() => a, (value) => a = value), data, out); + lentParam$u8(rs.place(() => held.value, (value2) => held.value = value2), data, out); + const at = rs.idx(v, 1); + lentParam$u8(rs.place(() => v.data[at], (value3) => v.data[at] = value3), data, out); + const at2 = rs.idx(v, 0); + let _19: rs.Place = rs.place(() => v.data[at2], (value4) => v.data[at2] = value4); + lentNested$u8(rs.place(() => _19, (value5) => _19 = value5), data, out); + put$conversions_Value_u8(a, out); + put$conversions_Value_u8(held.value, out); + put$conversions_Value_u8(v.data[rs.idx(v, 0)], out); + put$conversions_Value_u8(v.data[rs.idx(v, 1)], out); +} + /** [`lent_param`] of what a `&mut` to a `&mut` points at. */ export function lentNested$Option_u16(to: rs.Place>, data: rs.ByteSlice, out: rs.ByteVec): void { lentParam$Option_u16(to.v, data, out); @@ -1996,6 +2162,11 @@ export function lentNested$u64(to: rs.Place>, data: rs.ByteSlic lentParam$u64(to.v, data, out); } +/** [`lent_param`] of what a `&mut` to a `&mut` points at. */ +export function lentNested$u8(to: rs.Place>, data: rs.ByteSlice, out: rs.ByteVec): void { + lentParam$u8(to.v, data, out); +} + /** A cell of what a `&mut` whose place only the caller knows points at, written through. */ export function setThrough$Option_u16(to: rs.Place, value: rs.u16 | undefined): rs.u16 | undefined { return rs.cellReplace(rs.cellOfPlace(to), value); @@ -4827,6 +4998,47 @@ export function lentOne$u64(data: rs.ByteSlice, out: rs.ByteVec): void { put$conversions_Value_u64(a, out); } +/** + * Slices and arrays of one value of a local, a field, and an element, written through as + * a whole, the old value read out, and through std's slice methods. + */ +export function lentOne$u8(data: rs.ByteSlice, out: rs.ByteVec): void { + const a = new Uint8Array(1); + const c = new Uint8Array(1); + let mirror!: rs.ByteMirror | undefined; + const _6: [rs.u8, rs.u8, rs.u8] = [make$conversions_Value_u8(data, 0), make$conversions_Value_u8(data, 3), make$conversions_Value_u8(data, 6)]; + a[0] = _6[0]; + const b: rs.u8 = _6[1]; + c[0] = _6[2]; + const held: Held.Held = new Held.Held(make$conversions_Value_u8(data, 1)); + const v: rs.ByteVec = rs.byteVecFrom(Uint8Array.of(make$conversions_Value_u8(data, 2), make$conversions_Value_u8(data, 4))); + const s: rs.ByteSlice = new rs.ByteSlice(a, 0, 1); + rs.byteVecPush(out, s.length & 0xff); + rs.idx(s, 0); + const _28: rs.u8 = make$conversions_Value_u8(data, 5); + const old4 = s.data[s.start]; + s.data[s.start] = _28; + put$conversions_Value_u8(old4, out); + put$conversions_Value_u8(a[0], out); + const borrowed = held; + const old2: rs.u8 = writeFirst$u8((mirror = rs.byteMirror(rs.place(() => borrowed.value, (value) => borrowed.value = value))).s, make$conversions_Value_u8(data, 7)); + rs.byteMirrorFlush(mirror); + put$conversions_Value_u8(old2, out); + put$conversions_Value_u8(held.value, out); + put$conversions_Value_u8(writeFirst$u8(new rs.ByteSlice(v.data, rs.idx(v, 1), 1), make$conversions_Value_u8(data, 8)), out); + put$conversions_Value_u8(v.data[rs.idx(v, 1)], out); + rs.byteSliceFill(new rs.ByteSlice(c, 0, 1), b); + const _58: rs.ByteSlice = new rs.ByteSlice(a, 0, 1); + const iterS: Uint8Array = _58.data; + let iterI: number = _58.start; + const iterE: number = _58.start + _58.length; + while (iterI < iterE) { + iterS[iterI++] = make$conversions_Value_u8(data, 9); + } + put$conversions_Value_u8(c[0], out); + put$conversions_Value_u8(a[0], out); +} + /** * `binary_search` of each value `data` makes in a sorted run of them, where equal ones * repeat: `Ok` or `Err` as a byte, then the index. Then the same by a closure that logs @@ -8049,4 +8261,337 @@ export function readStr$ref_str(s: string, out: rs.ByteVec): void { rs.byteVecExtendFromSlice(out, rs.stringAsBytes(s)); } +/** Writes `value` through a slice of one of whatever byte `b` points at. */ +export function fillOne(b: rs.Place, value: rs.u8): void { + let mirror!: rs.ByteMirror | undefined; + rs.byteSliceFill((mirror = rs.byteMirror(b)).s, value); + rs.byteMirrorFlush(mirror); +} + +/** Pushes the byte a slice of one of `b` reads, then adds `by` through it. */ +export function readAdd(b: rs.Place, by: rs.u8, out: rs.ByteVec): void { + let mirror!: rs.ByteMirror | undefined; + const s: rs.ByteSlice = (mirror = rs.byteMirror(b)).s; + rs.byteVecPush(out, s.data[s.start]); + const _9: rs.u8 = rs.u8WrappingAdd(s.data[s.start], by); + s.data[s.start] = _9; + rs.byteMirrorFlush(mirror); +} + +/** A raw pointer to the first byte of `s`, which names none of its regions. */ +export function first(s: rs.ByteSlice): rs.Place { + return rs.byteSliceAsPtr(s); +} + +/** Puts a raw pointer to the first byte of `s` where `into` points. */ +export function stash(s: rs.ByteSlice, into: rs.Place | undefined>): void { + into.v = rs.byteSliceAsPtr(s); +} + +/** Puts a pointer to the first byte of `s` where `into` points, as std's `NonNull`. */ +export function stashNonNull(s: rs.ByteSlice, into: rs.Place | undefined>): void { + const pointer = rs.byteSliceAsPtr(s); + into.v = pointer === rs.NULL ? undefined : pointer; +} + +/** + * The first byte of `s`, with a lifetime its signature makes up, which names none of + * the slice's regions. + * + * # Safety + * + * The byte must be used only while `s` could be. + */ +export function detached(s: rs.ByteSlice): rs.Place { + return rs.byteSliceAsPtr(s); +} + +/** Adds `by` to the byte `s` holds, then gives it back, as an error if it is odd. */ +export function addChecked(s: rs.ByteSlice, by: rs.u8): rs.u8 | undefined { + rs.idx(s, 0); + const _3: rs.u8 = rs.u8WrappingAdd(s.data[s.start], by); + rs.idx(s, 0); + s.data[s.start] = _3; + rs.idx(s, 0); + if (!(rs.u8Rem(s.data[s.start], 2) === 0)) { + rs.idx(s, 0); + out$u8.err = s.data[s.start]; + return undefined; + } + rs.idx(s, 0); + return s.data[s.start]; +} + +/** [`add_checked`] of a slice of one of `b`, whose result it returns as is. */ +export function addThrough(b: rs.Place, by: rs.u8): rs.u8 | undefined { + let mirror!: rs.ByteMirror | undefined; + const part: rs.u8 | undefined = addChecked((mirror = rs.byteMirror(b)).s, by); + const err: rs.u8 | undefined = part === undefined ? out$u8.err as rs.u8 : undefined; + rs.byteMirrorFlush(mirror); + if (err !== undefined) { + out$u8.err = err as rs.u8; + return undefined; + } + return part as rs.u8; +} + +/** + * Slices of one byte where it is, each read and written, and the byte read where it is + * after: in a field, a tuple's, an array's, an `Option`'s, a `MaybeUninit`'s, a variable, + * through a parameter and a `&mut` to a `&mut`, kept in a closure, a `Vec`, a copy, a + * raw pointer, and a reference whose lifetime unsafe code made up, swapped, made again + * in a loop with the byte written between, and in a function returning what a call given + * one returns. + */ +export function bytePlaces(_kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): void { + let _215!: rs.u8; + let _339!: Uint8Array; + let mirror!: rs.ByteMirror | undefined; + let mirror10!: rs.ByteMirror | undefined; + let mirror11!: rs.ByteMirror | undefined; + let mirror12!: rs.ByteMirror | undefined; + let mirror13!: rs.ByteMirror | undefined; + let mirror14!: rs.ByteMirror | undefined; + let mirror15!: rs.ByteMirror | undefined; + let mirror16!: rs.ByteMirror | undefined; + let mirror17!: rs.ByteMirror | undefined; + let mirror18!: rs.ByteMirror | undefined; + let mirror2!: rs.ByteMirror | undefined; + let mirror3!: rs.ByteMirror | undefined; + let mirror4!: rs.ByteMirror | undefined; + let mirror5!: rs.ByteMirror | undefined; + let mirror6!: rs.ByteMirror | undefined; + let mirror7!: rs.ByteMirror | undefined; + let mirror8!: rs.ByteMirror | undefined; + let mirror9!: rs.ByteMirror | undefined; + const at: (a0: rs.usize) => rs.u8 = (i2: rs.usize): rs.u8 => { + return option_unwrapOr$1fr498u(rs.byteSliceGet(data, i2), i2 & 0xff); + }; + const h: Header.Header = { tag: at(0), pair: [at(1), at(2)] as [rs.u8, rs.u8], bytes: Uint8Array.of(at(3), at(4)), payload: then$1ldvmdq(rs.u8Rem(at(5), 3) !== 0, (): rs.u8 => { + return at(6); + }), uninit: (0 as unknown) as rs.u8 }; + const cursor: Cursor.Cursor = { dataData: data.data, dataStart: data.start, dataLen: data.length }; + const borrowed = h; + const _38: rs.ByteSlice = (mirror = rs.byteMirror(rs.place(() => borrowed.tag, (value) => borrowed.tag = value))).s; + const _36: boolean = Cursor.readExact(cursor, _38); + rs.byteMirrorFlush(mirror); + rs.byteVecPush(out, _36 ? 1 : 0); + const borrowed2 = h.pair; + const _44: rs.ByteSlice = (mirror2 = rs.byteMirror(rs.place(() => borrowed2[1], (value2) => borrowed2[1] = value2))).s; + const _42: boolean = Cursor.readExact(cursor, _44); + rs.byteMirrorFlush(mirror2); + rs.byteVecPush(out, _42 ? 1 : 0); + rs.byteVecPush(out, Cursor.readExact(cursor, new rs.ByteSlice(h.bytes, 1, 1)) ? 1 : 0); + Header.put(h, out); + const borrowed3 = h; + const s: rs.ByteSlice = h.payload === undefined ? rs.byteSliceFrom(new Uint8Array(0)) : (mirror3 = rs.byteMirror(rs.place(() => borrowed3.payload as rs.u8, (value3) => borrowed3.payload = value3))).s; + rs.byteVecPush(out, s.length & 0xff); + rs.byteSliceFill(s, at(7)); + rs.byteMirrorFlush(mirror3); + const _69: rs.u8 = at(8); + Header.bump(h, _69); + Header.put(h, out); + const borrowed4 = h; + const s2: rs.ByteSlice = (mirror4 = rs.byteMirror(rs.place(() => borrowed4.tag, (value4) => borrowed4.tag = value4))).s; + rs.idx(s2, 0); + s2.data[s2.start] = s2.data[s2.start] ^ 85; + rs.byteMirrorFlush(mirror4); + const t: rs.ByteSlice = rs.byteSliceRange(s2, 0); + rs.byteMirrorFlush(mirror4); + rs.idx(t, 0); + const _81: rs.u8 = rs.intRotateLeft(t.data[t.start], 1, 8, false); + rs.idx(t, 0); + t.data[t.start] = _81; + rs.byteMirrorFlush(mirror4); + const _91: rs.u8 = at(9); + const borrowed5 = h.pair; + const _94: rs.ByteSlice = (mirror5 = rs.byteMirror(rs.place(() => borrowed5[0], (value5) => borrowed5[0] = value5))).s; + _94.data.set(Uint8Array.of(_91), _94.start); + rs.byteMirrorFlush(mirror5); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(h.tag, h.pair[0])), undefined, true); + const borrowed6 = h.pair; + const _102: rs.ByteSlice = (mirror6 = rs.byteMirror(rs.place(() => borrowed6[0], (value6) => borrowed6[0] = value6))).s; + rs.idx(_102, 0); + const borrowed7 = h.pair; + const _109: rs.ByteSlice = (mirror7 = rs.byteMirror(rs.place(() => borrowed7[1], (value7) => borrowed7[1] = value7))).s; + rs.idx(_109, 0); + const swapped = _102.data[_102.start]; + _102.data[_102.start] = _109.data[_109.start]; + _109.data[_109.start] = swapped; + rs.byteMirrorFlush(mirror6); + rs.byteMirrorFlush(mirror7); + const borrowed8 = h; + rs.byteSliceCopyFromSlice((mirror8 = rs.byteMirror(rs.place(() => borrowed8.tag, (value8) => borrowed8.tag = value8))).s, rs.byteSliceFrom(Uint8Array.of(at(10)))); + rs.byteMirrorFlush(mirror8); + Header.put(h, out); + const borrowed9 = h; + const s3: rs.ByteSlice = (mirror9 = rs.byteMirror(rs.place(() => borrowed9.tag, (value9) => borrowed9.tag = value9))).s; + const set: (a0: rs.u8) => undefined = (v4: rs.u8): undefined => { + rs.idx(s3, 0); + const _33: rs.u8 = rs.u8WrappingMul(s3.data[s3.start], 3); + rs.idx(s3, 0); + s3.data[s3.start] = _33 ^ v4; + }; + const part = at(11); + set(part); + rs.byteMirrorFlush(mirror9); + const part2 = at(12); + set(part2); + rs.byteMirrorFlush(mirror9); + rs.byteVecPush(out, h.tag); + const borrowed10 = h.pair; + const _145: rs.ByteSlice = (mirror10 = rs.byteMirror(rs.place(() => borrowed10[0], (value10) => borrowed10[0] = value10))).s; + const borrowed11 = h.pair; + const v: rs.Vec = rs.vecFrom([_145, (mirror11 = rs.byteMirror(rs.place(() => borrowed11[1], (value11) => borrowed11[1] = value11))).s]); + rs.byteMirrorFlush(mirror10); + rs.byteMirrorFlush(mirror11); + const iterS: rs.ByteSlice[] = v.items; + let iterI: number = 0; + const iterE: number = v.items.length; + rs.byteMirrorFlush(mirror10); + rs.byteMirrorFlush(mirror11); + while (true) { + const _152: number | rs.Done = iterI < iterE ? iterI++ : rs.DONE; + rs.byteMirrorFlush(mirror10); + rs.byteMirrorFlush(mirror11); + if (_152 === rs.DONE) { + break; + } + rs.idx(iterS[_152], 0); + const _346: rs.ByteSlice = iterS[_152]; + const _156: rs.u8 = rs.u8WrappingAdd(_346.data[_346.start], at(13)); + rs.idx(iterS[_152], 0); + const _348: rs.ByteSlice = iterS[_152]; + _348.data[_348.start] = _156; + rs.byteMirrorFlush(mirror10); + rs.byteMirrorFlush(mirror11); + } + const _170: rs.Slice = rs.sliceFrom(v.items); + rs.byteMirrorFlush(mirror10); + rs.byteMirrorFlush(mirror11); + rs.sliceSwap(_170, 0, 1); + rs.byteMirrorFlush(mirror10); + rs.byteMirrorFlush(mirror11); + const at2 = rs.idx(v.items, 0); + rs.byteMirrorFlush(mirror10); + rs.byteMirrorFlush(mirror11); + rs.idx(v.items[at2], 0); + const _350: rs.ByteSlice = v.items[at2]; + const _351: rs.ByteSlice = v.items[at2]; + _350.data[_350.start] = _351.data[_351.start] ^ 1; + rs.byteMirrorFlush(mirror10); + rs.byteMirrorFlush(mirror11); + rs.byteMirrorFlush(mirror10); + rs.byteMirrorFlush(mirror11); + Header.put(h, out); + const borrowed12 = h; + const s4: rs.ByteSlice = (mirror12 = rs.byteMirror(rs.place(() => borrowed12.tag, (value12) => borrowed12.tag = value12))).s; + rs.idx(s4, 0); + const _184: rs.u8 = rs.u8WrappingAdd(s4.data[s4.start], 1); + rs.idx(s4, 0); + s4.data[s4.start] = _184; + rs.byteMirrorFlush(mirror12); + const copy: rs.ByteVec = rs.byteSliceToByteVec(s4); + rs.byteMirrorFlush(mirror12); + const _196: rs.u8 = at(14); + h.tag = _196; + rs.byteVecExtendFromSlice(out, rs.byteVecSlice(copy)); + rs.byteVecPush(out, h.tag); + const borrowed13 = h.pair; + const s5: rs.ByteSlice = rs.byteMirror(rs.place(() => borrowed13[1], (value13) => borrowed13[1] = value13)).s; + rs.idx(s5, 0); + rs.byteVecPush(out, s5.data[s5.start]); + let iterStart: rs.u8 = 0; + const iterEnd: rs.u8 = 3; + while ((_215 = iterStart++) < iterEnd) { + const i: rs.u8 = _215; + const borrowed14 = h; + const _221: rs.ByteSlice = (mirror13 = rs.byteMirror(rs.place(() => borrowed14.tag, (value14) => borrowed14.tag = value14))).s; + Cursor.readExact(cursor, _221); + rs.byteMirrorFlush(mirror13); + const _223: rs.u8 = rs.u8WrappingAdd(h.tag, i); + h.tag = _223; + rs.byteVecPush(out, h.tag); + } + const _227: rs.u8 = at(15); + const borrowed15 = h; + const _231: rs.ByteSlice = (mirror14 = rs.byteMirror(rs.place(() => borrowed15.uninit, (value15) => borrowed15.uninit = value15))).s; + rs.idx(_231, 0); + _231.data[_231.start] = _227; + rs.byteMirrorFlush(mirror14); + rs.byteVecPush(out, h.uninit); + const _242: rs.u8 = at(16); + fillOne(rs.place(() => h.tag, (value16) => h.tag = value16), _242); + const _247: rs.u8 = at(17); + readAdd(rs.place(() => h.pair[0], (value17) => h.pair[0] = value17), _247, out); + const _254: rs.u8 = at(18); + readAdd(rs.place(() => h.bytes[0], (value18) => h.bytes[0] = value18), _254, out); + let b: rs.u8 = at(19); + const _262: rs.u8 = at(20); + readAdd(rs.place(() => b, (value19) => b = value19), _262, out); + const r: rs.Place = rs.place(() => h.pair[1], (value20) => h.pair[1] = value20); + readAdd(r, at(21), out); + fillOne(r, at(22)); + rs.byteVecPush(out, b); + Header.put(h, out); + const borrowed16 = h; + const p: rs.Place = first((mirror15 = rs.byteMirror(rs.place(() => borrowed16.tag, (value21) => borrowed16.tag = value21))).s); + rs.byteMirrorFlush(mirror15); + const _284: rs.u8 = p.v; + rs.byteMirrorFlush(mirror15); + p.v = rs.u8WrappingAdd(_284, at(23)); + rs.byteMirrorFlush(mirror15); + rs.byteVecPush(out, h.tag); + let into = undefined as rs.Place | undefined; + const borrowed17 = h.pair; + stash((mirror16 = rs.byteMirror(rs.place(() => borrowed17[0], (value22) => borrowed17[0] = value22))).s, rs.place(() => into, (value23) => into = value23)); + rs.byteMirrorFlush(mirror16); + if (into !== undefined) { + const p2: rs.Place = into; + const _297: rs.u8 = at(24); + p2.v = p2.v ^ _297; + rs.byteMirrorFlush(mirror16); + } + let into2 = undefined as rs.Place | undefined; + const borrowed18 = h.pair; + stashNonNull((mirror17 = rs.byteMirror(rs.place(() => borrowed18[1], (value24) => borrowed18[1] = value24))).s, rs.place(() => into2, (value25) => into2 = value25)); + rs.byteMirrorFlush(mirror17); + if (into2 !== undefined) { + const p3: rs.Place = into2; + const _309: rs.Place = p3; + rs.byteMirrorFlush(mirror17); + const _307: rs.u8 = rs.u8WrappingMul(_309.v, at(25)); + const _313: rs.Place = p3; + rs.byteMirrorFlush(mirror17); + _313.v = _307; + rs.byteMirrorFlush(mirror17); + } + const borrowed19 = h; + const b2: rs.Place = detached((mirror18 = rs.byteMirror(rs.place(() => borrowed19.tag, (value26) => borrowed19.tag = value26))).s); + rs.byteMirrorFlush(mirror18); + b2.v = rs.u8WrappingAdd(b2.v, at(26)); + rs.byteMirrorFlush(mirror18); + Header.put(h, out); + const _326: rs.u8 = at(27); + const part3: rs.u8 | undefined = addThrough(rs.place(() => h.tag, (value27) => h.tag = value27), _326); + const err: rs.u8 | undefined = part3 === undefined ? out$u8.err as rs.u8 : undefined; + const part4: rs.u8 | undefined = addThrough(rs.place(() => h.tag, (value28) => h.tag = value28), 1); + const err2: rs.u8 | undefined = part4 === undefined ? out$u8.err as rs.u8 : undefined; + const iter3: rs.Iter> = rs.iterSlice([(err !== undefined ? rs.Err(err as rs.u8) : rs.Ok(part3 as rs.u8)) as rs.Result, (err2 !== undefined ? rs.Err(err2 as rs.u8) : rs.Ok(part4 as rs.u8)) as rs.Result]); + while (true) { + const _334: rs.Result | rs.Done = iter3.next(); + if (_334 === rs.DONE) { + break; + } + const r2: rs.Result = _334; + if (r2.ok) { + _339 = Uint8Array.of(0, r2.value); + } else { + _339 = Uint8Array.of(1, r2.error); + } + rs.iterExtendBytes(out, rs.iterSlice(_339), undefined, true); + } + Header.put(h, out); +} + //# sourceMappingURL=conversions.ts.map diff --git a/ts/fixtures/lang/conversions/expected/core/bool.ts b/ts/fixtures/lang/conversions/expected/core/bool.ts index c60a4259b..abf43743b 100644 --- a/ts/fixtures/lang/conversions/expected/core/bool.ts +++ b/ts/fixtures/lang/conversions/expected/core/bool.ts @@ -9,4 +9,11 @@ export function thenSome$1gvhopg(self: boolean, t: rs.u16): rs.u16 | undefined { return t; } +export function then$1ldvmdq(self: boolean, f: () => rs.u8): rs.u8 | undefined { + if (!self) { + return undefined; + } + return f(); +} + //# sourceMappingURL=bool.ts.map diff --git a/ts/fixtures/lang/conversions/expected/core/option.ts b/ts/fixtures/lang/conversions/expected/core/option.ts index 69fbdba55..22841efa8 100644 --- a/ts/fixtures/lang/conversions/expected/core/option.ts +++ b/ts/fixtures/lang/conversions/expected/core/option.ts @@ -19,6 +19,13 @@ export function mapOr$14runhi(self: rs.Ordering | undefined, default_: rs.u8, f: return f(self); } +export function mapOr$0vxw5sq(self: rs.u8 | undefined, default_: rs.u8, f: (a0: rs.u8) => rs.u8): rs.u8 { + if (self === undefined) { + return default_; + } + return f(self); +} + export function clone$0wpatp7(self: rs.u16 | undefined | rs.None): rs.u16 | undefined | rs.None { if (self === rs.None) { return rs.None; diff --git a/ts/fixtures/lang/conversions/expected/lib.ts b/ts/fixtures/lang/conversions/expected/lib.ts index 0a7051530..3ecbccb9e 100644 --- a/ts/fixtures/lang/conversions/expected/lib.ts +++ b/ts/fixtures/lang/conversions/expected/lib.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { conversions } from "./conversions.ts"; +import { conversions, bytePlaces } from "./conversions.ts"; import * as Logged from "./conversions.Logged.ts"; import type * as Named from "./conversions.Named.ts"; @@ -53,12 +53,14 @@ export function drop$tuple2_conversions_Logged_u8(_1: [Logged.Logged, rs.u8]): v _1[0][Symbol.dispose](); } +export const out$u8: { err: unknown } = { err: undefined }; + /** * 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. */ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]> { - return rs.vecFrom([["conversions::conversions", conversions as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); + return rs.vecFrom([["conversions::conversions", conversions as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["conversions::byte_places", bytePlaces as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } //# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/lang/conversions/src/conversions.rs b/ts/fixtures/lang/conversions/src/conversions.rs index a2353443e..1bf23ecd9 100644 --- a/ts/fixtures/lang/conversions/src/conversions.rs +++ b/ts/fixtures/lang/conversions/src/conversions.rs @@ -230,9 +230,8 @@ struct Held { value: T, } -/// Runs each conversion of the type `kind` picks, but a slice of one `u8`, whose `&mut` -/// no slice can view, and [`searched`] of those that order; and [`referenced`] of one -/// type of each form JS keeps a value in. +/// Runs each conversion of the type `kind` picks, and [`searched`] of those that order; +/// and [`referenced`] of one type of each form JS keeps a value in. pub fn conversions(kind: u8, data: &[u8], out: &mut Vec) { match kind % 14 { 0 => { @@ -269,8 +268,7 @@ pub fn conversions(kind: u8, data: &[u8], out: &mut Vec) { referenced::>(data, out); } 12 => { - converted::(data, out); - searched::(data, out); + ordered::(data, out); referenced::(data, out); } _ => { @@ -763,3 +761,216 @@ fn read_str>(s: S, out: &mut Vec) { out.push(s.as_ref().len() as u8); out.extend_from_slice(s.as_ref().as_bytes()); } + +/// Reads bytes off the front of a slice, as a decoder does. +struct Cursor<'a> { + data: &'a [u8], +} + +impl Cursor<'_> { + /// Fills `buf` from the front, or leaves it as it is if too few are left. + fn read_exact(&mut self, buf: &mut [u8]) -> bool { + if self.data.len() < buf.len() { + return false; + } + let (head, rest) = self.data.split_at(buf.len()); + buf.copy_from_slice(head); + self.data = rest; + true + } +} + +/// Bytes in each place a `&mut u8` reaches: fields, a tuple's, an array's, an `Option`'s, +/// and a `MaybeUninit`'s. +struct Header { + tag: u8, + pair: (u8, u8), + bytes: [u8; 2], + payload: Option, + uninit: std::mem::MaybeUninit, +} + +impl Header { + /// Each byte, the `MaybeUninit`'s once written. + fn put(&self, out: &mut Vec) { + out.extend([self.tag, self.pair.0, self.pair.1, self.bytes[0], self.bytes[1]]); + out.push(self.payload.map_or(0xee, |p| p ^ 0x80)); + } + + /// Adds `by` to its tag through a slice of one of it. + fn bump(&mut self, by: u8) { + for x in std::slice::from_mut(&mut self.tag) { + *x = x.wrapping_add(by); + } + } +} + +/// Writes `value` through a slice of one of whatever byte `b` points at. +fn fill_one(b: &mut u8, value: u8) { + std::slice::from_mut(b).fill(value); +} + +/// Pushes the byte a slice of one of `b` reads, then adds `by` through it. +fn read_add(b: &mut u8, by: u8, out: &mut Vec) { + let s = std::array::from_mut(b); + out.push(s[0]); + s[0] = s[0].wrapping_add(by); +} + +/// A raw pointer to the first byte of `s`, which names none of its regions. +fn first(s: &mut [u8]) -> *mut u8 { + s.as_mut_ptr() +} + +/// Puts a raw pointer to the first byte of `s` where `into` points. +fn stash(s: &mut [u8], into: &mut Option<*mut u8>) { + *into = Some(s.as_mut_ptr()); +} + +/// Puts a pointer to the first byte of `s` where `into` points, as std's `NonNull`. +fn stash_non_null(s: &mut [u8], into: &mut Option>) { + *into = std::ptr::NonNull::new(s.as_mut_ptr()); +} + +/// The first byte of `s`, with a lifetime its signature makes up, which names none of +/// the slice's regions. +/// +/// # Safety +/// +/// The byte must be used only while `s` could be. +unsafe fn detached(s: &mut [u8]) -> &'static mut u8 { + // SAFETY: the caller uses the byte only while `s` could be. + unsafe { &mut *s.as_mut_ptr() } +} + +/// Adds `by` to the byte `s` holds, then gives it back, as an error if it is odd. +fn add_checked(s: &mut [u8], by: u8) -> Result { + s[0] = s[0].wrapping_add(by); + if s[0] % 2 == 0 { Ok(s[0]) } else { Err(s[0]) } +} + +/// [`add_checked`] of a slice of one of `b`, whose result it returns as is. +fn add_through(b: &mut u8, by: u8) -> Result { + add_checked(std::slice::from_mut(b), by) +} + +/// Slices of one byte where it is, each read and written, and the byte read where it is +/// after: in a field, a tuple's, an array's, an `Option`'s, a `MaybeUninit`'s, a variable, +/// through a parameter and a `&mut` to a `&mut`, kept in a closure, a `Vec`, a copy, a +/// raw pointer, and a reference whose lifetime unsafe code made up, swapped, made again +/// in a loop with the byte written between, and in a function returning what a call given +/// one returns. +pub fn byte_places(_kind: u8, data: &[u8], out: &mut Vec) { + let at = |i: usize| data.get(i).copied().unwrap_or(i as u8); + let mut h = Header { + tag: at(0), + pair: (at(1), at(2)), + bytes: [at(3), at(4)], + payload: (at(5) % 3 != 0).then(|| at(6)), + uninit: std::mem::MaybeUninit::uninit(), + }; + let mut cursor = Cursor { data }; + out.push(u8::from(cursor.read_exact(std::slice::from_mut(&mut h.tag)))); + out.push(u8::from(cursor.read_exact(std::slice::from_mut(&mut h.pair.1)))); + out.push(u8::from(cursor.read_exact(std::array::from_mut(&mut h.bytes[1])))); + h.put(out); + // An `Option`'s, of none or of the one it holds. + let s = h.payload.as_mut_slice(); + out.push(s.len() as u8); + s.fill(at(7)); + h.bump(at(8)); + h.put(out); + // Kept while it is written through, by itself and by what is made of it. + let s = std::slice::from_mut(&mut h.tag); + s[0] ^= 0x55; + let t = &mut s[..]; + t[0] = t[0].rotate_left(1); + *std::array::from_mut(&mut h.pair.0) = [at(9)]; + out.extend([h.tag, h.pair.0]); + // Two at once, swapped, and copied into. + std::mem::swap( + &mut std::slice::from_mut(&mut h.pair.0)[0], + &mut std::slice::from_mut(&mut h.pair.1)[0], + ); + std::slice::from_mut(&mut h.tag).copy_from_slice(&[at(10)]); + h.put(out); + // Through a closure, then a `Vec` of two. + let s = std::slice::from_mut(&mut h.tag); + let mut set = |v: u8| s[0] = s[0].wrapping_mul(3) ^ v; + set(at(11)); + set(at(12)); + out.push(h.tag); + let mut v: Vec<&mut [u8]> = vec![std::slice::from_mut(&mut h.pair.0), std::slice::from_mut(&mut h.pair.1)]; + for s in &mut v { + s[0] = s[0].wrapping_add(at(13)); + } + v.swap(0, 1); + v[0][0] ^= 1; + drop(v); + h.put(out); + // A copy made of it, read after the byte is written where it is. + let s = std::slice::from_mut(&mut h.tag); + s[0] = s[0].wrapping_add(1); + let copy = s.to_vec(); + h.tag = at(14); + out.extend_from_slice(©); + out.push(h.tag); + // Only read through, so nothing is written back. + let s = std::slice::from_mut(&mut h.pair.1); + out.push(s[0]); + // Made again each round, the byte written between. + for i in 0..3 { + cursor.read_exact(std::slice::from_mut(&mut h.tag)); + h.tag = h.tag.wrapping_add(i); + out.push(h.tag); + } + // A `MaybeUninit`'s, written through one. + std::slice::from_mut(&mut h.uninit)[0] = std::mem::MaybeUninit::new(at(15)); + // SAFETY: written just above. + out.push(unsafe { h.uninit.assume_init() }); + // Through a parameter: a field's, a tuple's, an array's, a variable's, and through a + // `&mut` to a `&mut`. + fill_one(&mut h.tag, at(16)); + read_add(&mut h.pair.0, at(17), out); + read_add(&mut h.bytes[0], at(18), out); + let mut b = at(19); + read_add(&mut b, at(20), out); + let mut r = &mut h.pair.1; + read_add(*&mut r, at(21), out); + fill_one(r, at(22)); + out.push(b); + h.put(out); + // Written through a raw pointer made of one, returned and stashed. + let p = first(std::slice::from_mut(&mut h.tag)); + // SAFETY: `p` points at `h.tag`, which nothing else uses until it is read below. + unsafe { *p = p.read().wrapping_add(at(23)) }; + out.push(h.tag); + let mut into = None; + stash(std::slice::from_mut(&mut h.pair.0), &mut into); + if let Some(p) = into { + // SAFETY: as above, for `h.pair.0`. + unsafe { *p ^= at(24) }; + } + let mut into = None; + stash_non_null(std::slice::from_mut(&mut h.pair.1), &mut into); + if let Some(p) = into { + // SAFETY: as above, for `h.pair.1`. + unsafe { *p.as_ptr() = (*p.as_ptr()).wrapping_mul(at(25)) }; + } + // Written through a reference returned with a lifetime the callee made up. + // SAFETY: `b` is used only before `h.tag` is read below. + let b = unsafe { detached(std::slice::from_mut(&mut h.tag)) }; + *b = b.wrapping_add(at(26)); + h.put(out); + // Through a function that returns what a call given one returns, as `Ok` and as `Err`: + // the second adds one, so one of the two is odd. + let first = add_through(&mut h.tag, at(27)); + let second = add_through(&mut h.tag, 1); + for r in [first, second] { + out.extend(match r { + Ok(v) => [0, v], + Err(v) => [1, v], + }); + } + h.put(out); +} diff --git a/ts/fixtures/lang/conversions/src/lib.rs b/ts/fixtures/lang/conversions/src/lib.rs index e18de1007..67af911db 100644 --- a/ts/fixtures/lang/conversions/src/lib.rs +++ b/ts/fixtures/lang/conversions/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![("conversions::conversions", conversions::conversions)] + vec![ + ("conversions::conversions", conversions::conversions), + ("conversions::byte_places", conversions::byte_places), + ] } diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index f7530611f..38de56363 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -181,7 +181,6 @@ fixtures/reject/src/pointers.rs:arc_from_borrow+1:12: `Arc::from_raw` of a point fixtures/reject/src/pointers.rs:arc_raw_slice+1:2: an `Arc`'s raw pointer to `[u32]`, which JS makes anew each time fixtures/reject/src/pointers.rs:array_of_one+1:2: comparing pointers to `[u32; 1]`, an array of one JS may make of its own for a value, as `array::from_ref` does fixtures/reject/src/pointers.rs:assigned_through+2:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -fixtures/reject/src/pointers.rs:byte_option_slice+1:4: a slice of one byte's place, which a byte slice can view only in an array of bytes fixtures/reject/src/pointers.rs:byte_pointer_ops+5:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in fixtures/reject/src/pointers.rs:byte_pointer_ops+6:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in fixtures/reject/src/pointers.rs:byte_pointer_ops+7:14: comparing `*mut [u8; 4]` @@ -195,7 +194,11 @@ fixtures/reject/src/pointers.rs:byte_pointer_parts+3:44: a raw pointer to `[u8; fixtures/reject/src/pointers.rs:byte_pointer_rebound+2:26: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there fixtures/reject/src/pointers.rs:byte_pointer_rebound+2:26: a reference to `[u8; 4]`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there fixtures/reject/src/pointers.rs:byte_pointer_rebound+3:10: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there -fixtures/reject/src/pointers.rs:byte_slice_of_place+1:2: a slice of one byte's place, which a byte slice can view only in an array of bytes +fixtures/reject/src/pointers.rs:byte_result_stored+1:21: a slice of one byte's place, which JS copies, kept where no variable of the function holds it, such as in memory a parameter points at or in what the function returns, so the copy is never written back +fixtures/reject/src/pointers.rs:byte_results_kept+3:24: a slice of one byte's place, which JS copies, that may still be used once the call makes another, which the variable written back from holds instead +fixtures/reject/src/pointers.rs:byte_slice_returned+1:2: a slice of one byte's place, which JS copies, kept where no variable of the function holds it, such as in memory a parameter points at or in what the function returns, so the copy is never written back +fixtures/reject/src/pointers.rs:byte_slice_stored+1:11: a slice of one byte's place, which JS copies, kept where no variable of the function holds it, such as in memory a parameter points at or in what the function returns, so the copy is never written back +fixtures/reject/src/pointers.rs:byte_slices_kept+3:13: a slice of one byte's place, which JS copies, that may still be used once the call makes another, which the variable written back from holds instead fixtures/reject/src/pointers.rs:cast_reassigned+2:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there fixtures/reject/src/pointers.rs:cell_field_replaced+4:21: a raw pointer to `std::cell::UnsafeCell` in `pointers::Hits`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:cell_reassigned+2:15: a raw pointer to `std::cell::UnsafeCell` in a variable assigned again while it may point there diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 6c7b82dbc..a8d466b37 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -183,7 +183,6 @@ fixtures/reject/src/pointers.rs:arc_from_borrow+1:12: `Arc::from_raw` of a point fixtures/reject/src/pointers.rs:arc_raw_slice+1:2: an `Arc`'s raw pointer to `[u32]`, which JS makes anew each time fixtures/reject/src/pointers.rs:array_of_one+1:2: comparing pointers to `[u32; 1]`, an array of one JS may make of its own for a value, as `array::from_ref` does fixtures/reject/src/pointers.rs:assigned_through+2:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -fixtures/reject/src/pointers.rs:byte_option_slice+1:4: a slice of one byte's place, which a byte slice can view only in an array of bytes fixtures/reject/src/pointers.rs:byte_pointer_ops+5:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in fixtures/reject/src/pointers.rs:byte_pointer_ops+6:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in fixtures/reject/src/pointers.rs:byte_pointer_ops+7:14: comparing `*mut [u8; 4]` @@ -197,7 +196,11 @@ fixtures/reject/src/pointers.rs:byte_pointer_parts+3:44: a raw pointer to `[u8; fixtures/reject/src/pointers.rs:byte_pointer_rebound+2:26: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there fixtures/reject/src/pointers.rs:byte_pointer_rebound+2:26: a reference to `[u8; 4]`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there fixtures/reject/src/pointers.rs:byte_pointer_rebound+3:10: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there -fixtures/reject/src/pointers.rs:byte_slice_of_place+1:2: a slice of one byte's place, which a byte slice can view only in an array of bytes +fixtures/reject/src/pointers.rs:byte_result_stored+1:21: a slice of one byte's place, which JS copies, kept where no variable of the function holds it, such as in memory a parameter points at or in what the function returns, so the copy is never written back +fixtures/reject/src/pointers.rs:byte_results_kept+3:24: a slice of one byte's place, which JS copies, that may still be used once the call makes another, which the variable written back from holds instead +fixtures/reject/src/pointers.rs:byte_slice_returned+1:2: a slice of one byte's place, which JS copies, kept where no variable of the function holds it, such as in memory a parameter points at or in what the function returns, so the copy is never written back +fixtures/reject/src/pointers.rs:byte_slice_stored+1:11: a slice of one byte's place, which JS copies, kept where no variable of the function holds it, such as in memory a parameter points at or in what the function returns, so the copy is never written back +fixtures/reject/src/pointers.rs:byte_slices_kept+3:13: a slice of one byte's place, which JS copies, that may still be used once the call makes another, which the variable written back from holds instead fixtures/reject/src/pointers.rs:cast_reassigned+2:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there fixtures/reject/src/pointers.rs:cell_field_replaced+4:21: a raw pointer to `std::cell::UnsafeCell` in `pointers::Hits`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:cell_reassigned+2:15: a raw pointer to `std::cell::UnsafeCell` in a variable assigned again while it may point there diff --git a/ts/fixtures/reject/src/pointers.rs b/ts/fixtures/reject/src/pointers.rs index 9e9cc2a78..e309df1c7 100644 --- a/ts/fixtures/reject/src/pointers.rs +++ b/ts/fixtures/reject/src/pointers.rs @@ -446,14 +446,55 @@ pub fn written_bytes(mut lanes: [u32; 2], mut b: u8) -> u32 { lanes[0] + u32::from(b) } -/// A slice of one `u8`'s place, which no `rs.ByteSlice` views. -pub fn byte_slice_of_place(b: &mut u8) { - std::slice::from_mut(b)[0] = 1; +/// A byte in a field, which no array of bytes holds. +pub struct Tagged { + pub tag: u8, } -/// A slice of an `Option`'s byte, which no `rs.ByteSlice` views either. -pub fn byte_option_slice(o: &mut Option) { - o.as_mut_slice().fill(1); +/// A slice of one byte in a field, returned: its copy would be written back after no +/// statement of the function. +pub fn byte_slice_returned(t: &mut Tagged) -> &mut [u8] { + std::slice::from_mut(&mut t.tag) +} + +/// A slice of one byte in a field, kept where a parameter points: no variable of the +/// function holds it. +pub fn byte_slice_stored<'a>(t: &'a mut Tagged, out: &mut Vec<&'a mut [u8]>) { + out.push(std::slice::from_mut(&mut t.tag)); +} + +/// Slices of one byte in each of the fields, kept together: the one variable holding +/// the copy the call makes can't keep them apart. +pub fn byte_slices_kept(ts: &mut [Tagged]) { + let mut kept = Vec::new(); + for t in ts.iter_mut() { + kept.push(std::slice::from_mut(&mut t.tag)); + } + for s in kept { + s[0] = 1; + } +} + +/// Whether the byte `s` holds is zero, as a name that borrows none of it. +fn zero_name(s: &mut [u8]) -> &'static str { + if s[0] == 0 { "zero" } else { "other" } +} + +/// What a call given a slice of one byte in a field returns, kept where a parameter +/// points: it could hold the slice whatever its lifetime says, and no variable of the +/// function holds it. +pub fn byte_result_stored(t: &mut Tagged, out: &mut Vec<&'static str>) { + out.push(zero_name(std::slice::from_mut(&mut t.tag))); +} + +/// What calls given slices of one byte in each of the fields return, kept together: +/// each could hold its slice whatever its lifetime says. +pub fn byte_results_kept(ts: &mut [Tagged]) -> usize { + let mut names = Vec::new(); + for t in ts.iter_mut() { + names.push(zero_name(std::slice::from_mut(&mut t.tag))); + } + names.len() } /// A pointer to a variable's object, which `clone_from` of the whole variable replaces. diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index ae0d56fcd..349e707a4 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -130,11 +130,14 @@ roots = [ "pointers::arc_raw_slice", "pointers::array_of_one", "pointers::assigned_through", - "pointers::byte_option_slice", "pointers::byte_pointer_ops", "pointers::byte_pointer_parts", "pointers::byte_pointer_rebound", - "pointers::byte_slice_of_place", + "pointers::byte_result_stored", + "pointers::byte_results_kept", + "pointers::byte_slice_returned", + "pointers::byte_slice_stored", + "pointers::byte_slices_kept", "pointers::cast_reassigned", "pointers::cell_field_replaced", "pointers::cell_reassigned", diff --git a/ts/runtime/rs.ts b/ts/runtime/rs.ts index c16f6cc38..6f937c40c 100644 --- a/ts/runtime/rs.ts +++ b/ts/runtime/rs.ts @@ -999,6 +999,46 @@ export function sliceIn(p: T): Slice { /** The arrays of one that `sliceOfPlace`'s slices of a place in no array read. */ const ONE = /* @__PURE__ */ new WeakSet(); +/** + * A slice `byteMirror` made of one byte's place, `s`: a view of the bytes it is in, or + * else a copy of its own, its mirror, which `byteMirrorFlush` writes back to `p`, the place, + * holding `last`, the byte it last wrote there. + */ +export class ByteMirror { + readonly s: ByteSlice; + readonly p: Place | undefined; + last: number; + + constructor(s: ByteSlice, p: Place | undefined) { + this.s = s; + this.p = p; + this.last = s.data[s.start]; + } +} + +/** + * `slice::from_mut` or `array::from_mut` of the byte at `p`: a view of the bytes it is in, + * or for a byte in no array of bytes, such as a field's, a mirror of it. + */ +export function byteMirror(p: Place): ByteMirror { + if (p instanceof AtIndex && p.array instanceof Uint8Array) return new ByteMirror(new ByteSlice(p.array, p.index, 1), undefined); + // A typed array of a length, then the byte, as `Uint8Array.of` takes twice as long. + const data = new Uint8Array(1); + data[0] = p.v; + return new ByteMirror(new ByteSlice(data, 0, 1), p); +} + +/** + * Write the byte of `m`'s mirror back to its place if it changed since it was last written + * there. Rust reaches the byte only through the slice while it is in use, and once it no + * longer is, the mirror no longer changes, so this writes nothing over a later write. + */ +export function byteMirrorFlush(m: ByteMirror | undefined): void { + if (m === undefined || m.p === undefined) return; + const byte = m.s.data[0]; + if (byte !== m.last) m.p.v = m.last = byte; +} + /** * Whether a whole write of an element of `s`, an array a slice holds, may land in the object * there, as one of `sliceIn`'s does, or of a place in one, which only `sliceOfPlace`'s can be. diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index ba6587f61..63d77fda8 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -1087,13 +1087,6 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // empty one. ("Option", "as_mut_slice") => { let inner = targ(0)?; - if crate::lower::byte(inner) { - let refused = self.err( - span, - "a slice of one byte's place, which a byte slice can view only in an array of bytes", - ); - return done(refused, true); - } let (target, payload) = self.payload_place(&args[0].node, inner, span, out)?; let Some(payload) = payload else { return done(target, true); @@ -1106,7 +1099,11 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let e = Expr::Cond(Box::new(none), Box::new(runs(Vec::new())), Box::new(runs(vec![target]))); return done(e, true); } - let some = if self.b.t.needs_place(inner) { + let bytes = crate::lower::byte(inner); + let some = if bytes { + let place = self.make_place(payload, target, span, out); + self.byte_slice_of(place, span) + } else if self.b.t.needs_place(inner) { Expr::rs("sliceOfPlace", vec![self.make_place(payload, target, span, out)]) } else if self.b.t.write_in(inner).is_some() { Expr::rs("sliceIn", vec![target]) @@ -1119,7 +1116,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { done( Expr::Cond( Box::new(none), - Box::new(Expr::rs("sliceFrom", vec![Expr::Array(vec![])])), + Box::new(match bytes { + true => Expr::rs( + "byteSliceFrom", + vec![Expr::New(Box::new(Expr::ident("Uint8Array")), vec![Expr::lit("0")])], + ), + false => Expr::rs("sliceFrom", vec![Expr::Array(vec![])]), + }), Box::new(some), ), true, @@ -1613,9 +1616,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { /// names, or else in place of the bytes or the object it is, which a cell lends as itself, /// as its `get` or `as_ptr` points at it, but a `Cell` of an object a pointer is the /// place of reads a copy of, as its writes replace the value it read, and a slice writes - /// its element into. A slice of one byte no array holds is refused, as is any of an - /// object JS can't write a whole value into, an `UnsafeCell` of an object a pointer is - /// the place of, such as a `Vec`, and any of a pinned `Box`. + /// its element into, or for a byte, as [`Self::byte_slice_of`] makes it. Any of an object + /// JS can't write a whole value into is refused, as is an `UnsafeCell` of an object a + /// pointer is the place of, such as a `Vec`, and any of a pinned `Box`. fn one_of( &mut self, owner: &str, @@ -1632,14 +1635,25 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { "Cell" | "UnsafeCell" => "cellOfPlace", _ => "sliceOfPlace", }; - let of = |made: Expr| Expr::rs(of_place, vec![made]); + let of_rs = |made: Expr| Expr::rs(of_place, vec![made]); let place_of = self.b.t.pointee(ty) == crate::lower::pointer::Pointee::Place; let of_object = |object: Expr| match owner { "Cell" if place_of => Expr::rs("cellIn", vec![object]), - "Cell" | "UnsafeCell" => of(Expr::rs("partPlace", vec![object])), + "Cell" | "UnsafeCell" => of_rs(Expr::rs("partPlace", vec![object])), _ => Expr::rs("sliceIn", vec![object]), }; let cell = owner != "raw" && owner != "array"; + let bytes = !cell && crate::lower::byte(ty); + if bytes && self.mirrors.views.contains(&self.at.block) { + let view = self.byte_view(place.local); + return Some( + view.unwrap_or_else(|| self.err(span, "a slice of one byte in an array translation can't name")), + ); + } + let of = |fx: &mut Self, made: Expr| match bytes { + true => fx.byte_slice_of(made, span), + false => of_rs(made), + }; // A `&mut [T; N]` is the slice it views: a slice of it is the array slice of that one, and a // cell of an array of arrays reads a copy of the arrays it views and writes into them. if let ty::Array(elem, len) = ty.kind() @@ -1664,12 +1678,6 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let n = len.try_to_target_usize(tcx).unwrap_or(0); return Some(Expr::rs("arraySliceOne", vec![slice, Expr::lit(n.to_string())])); } - if !cell && crate::lower::byte(ty) { - return Some(self.err( - span, - "a slice of one byte's place, which a byte slice can view only in an array of bytes", - )); - } // A whole write through what this makes lands in the object, so a value read out // before would change with it unless copied, as a `Cell`'s or a slice's read is, but // a pinned `Box`'s copy is another allocation. @@ -1698,11 +1706,12 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { { let target = target.clone(); let target = self.stable_target(deref, target, span, out); - return Some(of(self.place_of(target, None))); + let made = self.place_of(target, None); + return Some(of(self, made)); } if self.b.t.needs_place(ty) { let made = self.operand(op, span, out); - return Some(of(made)); + return Some(of(self, made)); } if self.b.t.write_in(ty).is_some() { let object = self.operand(op, span, out); diff --git a/ts/src/lower/mir/holders.rs b/ts/src/lower/mir/holders.rs index 68a1d988c..f59ed10f8 100644 --- a/ts/src/lower/mir/holders.rs +++ b/ts/src/lower/mir/holders.rs @@ -665,7 +665,7 @@ impl<'x, 'a, 'tcx> Trace<'x, 'a, 'tcx> { } /// Whether a value of `ty` could hold a pointer or a reference, in any field of any type. - fn carries(&self, ty: Ty<'tcx>) -> bool { + pub fn carries(&self, ty: Ty<'tcx>) -> bool { self.holds_pointer(ty, true) } @@ -676,9 +676,97 @@ impl<'x, 'a, 'tcx> Trace<'x, 'a, 'tcx> { self.holds_pointer(ty, false) } + /// Whether safe code could put a pointer or a reference in a value of `ty`: as + /// [`Self::carries`], but std's types hold only what their type arguments do, as the + /// pointers of their own are private. + pub fn takes_pointer(&self, ty: Ty<'tcx>) -> bool { + self.holds_pointer_as(ty, true, true) + } + + /// Whether a value of `ty` points at memory safe code could put a pointer or a + /// reference in, as [`Self::takes_pointer`] tells, through a pointer it holds. + pub fn points_at_taker(&self, ty: Ty<'tcx>) -> bool { + let tcx = self.t.tcx; + let mut seen = FxHashSet::default(); + let mut stack = vec![ty]; + while let Some(ty) = stack.pop() { + if !seen.insert(ty) { + continue; + } + match ty.kind() { + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Str + | ty::Never + | ty::FnDef(..) + | ty::FnPtr(..) => {} + ty::Ref(_, inner, _) | ty::RawPtr(inner, _) if self.takes_pointer(*inner) => return true, + ty::Ref(..) | ty::RawPtr(..) => {} + ty::Array(elem, _) | ty::Slice(elem) | ty::Pat(elem, _) => stack.push(*elem), + ty::Tuple(tys) => stack.extend(tys.iter()), + ty::Closure(_, args) => stack.extend(args.as_closure().upvar_tys()), + // A raw pointer of std's points as one does. + ty::Adt(adt, args) if raw_carrier(tcx, adt.did()) => { + if args.types().any(|t| self.takes_pointer(t)) { + return true; + } + } + ty::Adt(adt, args) if crate::lower::library::is_std(tcx, adt.did()) => stack.extend(args.types()), + ty::Adt(adt, args) => { + stack.extend(adt.all_fields().map(|field| field_ty(tcx, self.env, field.did, args))) + } + _ => return true, + } + } + false + } + + /// Whether a value of `ty` could hold a raw pointer, which names no region of the + /// reference it was made from: in a field, behind a reference, in a std type's type + /// argument or a [`raw_carrier`], in a type parameter's value, or in a trait object's. + pub fn hides_raw_pointer(&self, ty: Ty<'tcx>) -> bool { + let tcx = self.t.tcx; + let mut seen = FxHashSet::default(); + let mut stack = vec![ty]; + while let Some(ty) = stack.pop() { + if !seen.insert(ty) { + continue; + } + match ty.kind() { + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Str + | ty::Never + | ty::FnDef(..) + | ty::FnPtr(..) => {} + ty::Ref(_, inner, _) | ty::Array(inner, _) | ty::Slice(inner) | ty::Pat(inner, _) => stack.push(*inner), + ty::Tuple(tys) => stack.extend(tys.iter()), + ty::Closure(_, args) => stack.extend(args.as_closure().upvar_tys()), + ty::Adt(adt, _) if raw_carrier(tcx, adt.did()) => return true, + ty::Adt(adt, args) if crate::lower::library::is_std(tcx, adt.did()) => stack.extend(args.types()), + ty::Adt(adt, args) => { + stack.extend(adt.all_fields().map(|field| field_ty(tcx, self.env, field.did, args))) + } + _ => return true, + } + } + false + } + /// Whether a value of `ty` could hold a pointer or a reference, in any field of any /// type, and with `params`, in a type parameter's value. fn holds_pointer(&self, ty: Ty<'tcx>, params: bool) -> bool { + self.holds_pointer_as(ty, params, false) + } + + /// [`Self::holds_pointer`], or with `std_args`, [`Self::takes_pointer`]. + fn holds_pointer_as(&self, ty: Ty<'tcx>, params: bool, std_args: bool) -> bool { let mut seen = FxHashSet::default(); let mut stack = vec![ty]; while let Some(ty) = stack.pop() { @@ -697,6 +785,12 @@ impl<'x, 'a, 'tcx> Trace<'x, 'a, 'tcx> { | ty::FnPtr(..) => {} ty::Array(elem, _) | ty::Slice(elem) | ty::Pat(elem, _) => stack.push(*elem), ty::Tuple(tys) => stack.extend(tys.iter()), + ty::Adt(adt, args) if std_args && crate::lower::library::is_std(self.t.tcx, adt.did()) => { + if args.regions().next().is_some() || raw_carrier(self.t.tcx, adt.did()) { + return true; + } + stack.extend(args.types()); + } ty::Adt(adt, args) => stack.extend( adt.all_fields() .map(|field| field_ty(self.t.tcx, self.env, field.did, args)), @@ -1686,3 +1780,17 @@ impl<'tcx> Visitor<'tcx> for Reads<'_> { self.found |= self.holders.contains(local); } } + +/// Whether `did` is one of std's types that hold a raw pointer safe code puts in and gets +/// back, unlike std's others, whose pointers are private. +fn raw_carrier(tcx: ty::TyCtxt<'_>, did: DefId) -> bool { + matches!( + crate::lower::library::std_path(tcx, did).as_slice(), + [.., m, name] if matches!( + (m.as_str(), name.as_str()), + ("non_null", "NonNull") + | ("atomic", "AtomicPtr") + | ("wake", "RawWaker" | "Waker" | "LocalWaker" | "Context") + ) + ) +} diff --git a/ts/src/lower/mir/mirror.rs b/ts/src/lower/mir/mirror.rs new file mode 100644 index 000000000..4845f8acb --- /dev/null +++ b/ts/src/lower/mir/mirror.rs @@ -0,0 +1,530 @@ +//! Slices of one byte's place. A `&mut [u8]` is an `rs.ByteSlice`, a view of a +//! `Uint8Array`, while a `&mut u8` is an `rs.Place` wherever the byte is. So +//! `slice::from_mut`, `array::from_mut`, and `Option::as_mut_slice` of one view the bytes it +//! is in, or for a byte in no array of bytes, such as a field's, copy it into a buffer of +//! its own, its mirror, an `rs.ByteMirror`. Rust reaches the byte only through the slice +//! while the slice may still be used, so the mirror is written back after each write +//! through a local that may hold the slice, or what is made of it, and each call or drop +//! given one, whenever its byte changed since the last write-back. Once nothing uses the slice, the mirror no longer +//! changes, so a later write-back writes nothing. A slice that may be kept where no local of +//! the body holds it, such as in memory a parameter points at, a coroutine's state, or what +//! the function returns, is refused, as is one an earlier run of the call made that may still +//! be used. + +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_index::bit_set::DenseBitSet; +use rustc_middle::mir::visit::Visitor; +use rustc_middle::mir::{self, BasicBlock, Local, Location, Operand, TerminatorKind}; +use rustc_middle::ty::{self, Ty}; +use rustc_mir_dataflow::Analysis; +use rustc_mir_dataflow::impls::MaybeLiveLocals; + +use super::holders::Trace; +use super::{Fx, Slot}; +use crate::lower::library; +use crate::ts::{Expr, Stmt, StmtKind}; + +/// Why a slice of one byte's place kept where no variable of the function holds it is +/// refused. +const KEPT: &str = "a slice of one byte's place, which JS copies, kept where no variable of the function holds it, such as in memory a parameter points at or in what the function returns, so the copy is never written back"; + +/// Why a slice of one byte's place that may still be used once its call makes another is +/// refused. +const AGAIN: &str = "a slice of one byte's place, which JS copies, that may still be used once the call makes another, which the variable written back from holds instead"; + +/// The slices of one byte's place a body makes, and where each mirror is written back. +#[derive(Default)] +pub(super) struct Mirrors { + /// By the block whose call makes one: how it is held. + made: FxHashMap, + /// The blocks whose call makes one of a byte the body shows is in an array of bytes, + /// whose slice is a view of it. + pub views: FxHashSet, + /// The mirrors to write back after each statement. + after: FxHashMap>, + /// The mirrors to write back as each block starts, after a call or drop that may have + /// written them, by that block or one that only goes on to it. + entry: FxHashMap>, +} + +/// How a slice of one byte's place a call makes is held. +enum Made { + /// Refused, for this reason. + Refused(&'static str), + /// Nothing writes through it, so its mirror, if any, needs no variable to be written back. + Unwritten, + /// In this variable, which its mirror, if any, is written back from. + Var(String), +} + +impl<'tcx> Fx<'_, '_, '_, 'tcx> { + /// Whether `func` makes a slice of the one byte a `&mut` points at, or an `Option` holds. + pub(super) fn byte_slice_call(&self, func: &Operand<'tcx>) -> bool { + let tcx = self.tcx; + let ty::FnDef(def_id, gargs) = *func.ty(&self.mir.local_decls, tcx).kind() else { + return false; + }; + if !library::is_std(tcx, def_id) || !gargs.skip_binder().types().next().is_some_and(crate::lower::byte) { + return false; + } + match library::key(tcx, def_id) { + Some((owner, name)) => match (owner.as_str(), name.as_str()) { + ("raw", "from_mut") => library::std_path(tcx, def_id) == ["slice", "raw", "from_mut"], + ("array", "from_mut") | ("Option", "as_mut_slice") => true, + _ => false, + }, + None => false, + } + } + + /// Each call making a slice of one byte's place, its mirror's variable, and where that + /// is written back. + pub(super) fn find_mirrors(&mut self) { + let mir = self.mir; + let mut live = None; + let mut mirrors = Mirrors::default(); + for (bb, data) in mir.basic_blocks.iter_enumerated() { + let TerminatorKind::Call { + func, + args, + destination, + .. + } = &data.terminator().kind + else { + continue; + }; + if data.is_cleanup || !self.byte_slice_call(func) { + continue; + } + if self.byte_in_array(&args[0].node) { + mirrors.views.insert(bb); + continue; + } + let site = mir.terminator_loc(bb); + let Some(held) = self.slice_holders(*destination) else { + mirrors.made.insert(bb, Made::Refused(KEPT)); + continue; + }; + // What may still hold the slice an earlier run of the call made, which the one + // variable can't keep apart from this run's. Only a call in a loop runs again + // with this body's variables. + if self.in_loop(bb) { + let live = live.get_or_insert_with(|| { + MaybeLiveLocals + .iterate_to_fixpoint(self.tcx, mir, None) + .into_results_cursor(mir) + }); + live.seek_after_primary_effect(site); + if held.iter().any(|h| live.get().contains(h)) { + mirrors.made.insert(bb, Made::Refused(AGAIN)); + continue; + } + } + let (mut after, mut entry) = (Vec::new(), Vec::new()); + for (block, data) in mir.basic_blocks.iter_enumerated() { + if data.is_cleanup { + continue; + } + // A write through what may hold it. + for (statement_index, stmt) in data.statements.iter().enumerate() { + if let mir::StatementKind::Assign(assign) = &stmt.kind + && held.contains(assign.0.local) + && assign.0.is_indirect() + { + after.push(Location { block, statement_index }); + } + } + // A call or drop given what may hold it, which may write through it. + let term = data.terminator(); + if block == bb + || !matches!(term.kind, TerminatorKind::Call { .. } | TerminatorKind::Drop { .. }) + || !reads(&held, |v| v.visit_terminator(term, Location::START)) + { + continue; + } + entry.extend(term.successors().filter(|&to| !mir.basic_blocks[to].is_cleanup)); + } + if after.is_empty() && entry.is_empty() { + mirrors.made.insert(bb, Made::Unwritten); + continue; + } + let name = self.b.fresh("mirror"); + for at in after { + mirrors.after.entry(at).or_default().push(name.clone()); + } + for to in entry { + let due = mirrors.entry.entry(to).or_default(); + if !due.contains(&name) { + due.push(name.clone()); + } + } + mirrors.made.insert(bb, Made::Var(name)); + } + // A call that returns its result as is returns before its target block starts, so + // one with a mirror due there returns through that block instead. + self.returning + .retain(|at| match &mir.basic_blocks[at.block].terminator().kind { + TerminatorKind::Call { target: Some(to), .. } + if at.statement_index == mir[at.block].statements.len() => + { + !mirrors.entry.contains_key(to) + } + _ => true, + }); + self.mirrors = mirrors; + } + + /// Whether `op`, the `&mut u8` a slice of one is made of, is an alias of a byte the body + /// shows is in an array of bytes: an element of one, by a place or by `index_mut` of a + /// `Vec`, a slice, or an array of bytes, or a byte variable kept in a buffer of its own. + fn byte_in_array(&self, op: &Operand<'tcx>) -> bool { + let tcx = self.tcx; + let Some(local) = op.place().and_then(|p| p.as_local()) else { + return false; + }; + let bytes = |ty: ty::Ty<'tcx>| match ty.kind() { + ty::Slice(elem) | ty::Array(elem, _) => crate::lower::byte(*elem), + ty::Adt(_, args) => self.b.t.std_of(ty) == Some(library::Std::Vec) && crate::lower::byte(args.type_at(0)), + _ => false, + }; + let mut local = local; + for _ in 0..self.mir.local_decls.len() { + if self.defs[local] != 1 { + return false; + } + let Some(rvalue) = self.def_rvalue(local) else { + // By `index_mut` of an element. + return self.mir.basic_blocks.iter().any(|data| { + let TerminatorKind::Call { + func, + args, + destination, + .. + } = &data.terminator().kind + else { + return false; + }; + let ty::FnDef(def_id, gargs) = *func.ty(&self.mir.local_decls, tcx).kind() else { + return false; + }; + destination.as_local() == Some(local) + && library::key(tcx, def_id).is_some_and(|(o, n)| o == "IndexMut" && n == "index_mut") + && gargs.skip_binder().types().next().is_some_and(bytes) + && args + .get(1) + .is_some_and(|i| i.node.ty(&self.mir.local_decls, tcx).is_usize()) + }); + }; + // A reference lowers to an alias of its place only where its slot is one; what + // `index_mut` returns, to one of the element it lends wherever it is read once. + let mir::Rvalue::Ref(_, _, place) = rvalue else { + return false; + }; + if !matches!(self.slots[local], Slot::Alias(_)) { + return false; + } + // Through a reborrow `&mut *r`. + if place.projection.as_slice() == [mir::ProjectionElem::Deref] { + local = place.local; + continue; + } + return match place.iter_projections().last() { + None => matches!(self.slots[place.local], Slot::Byte(_)), + Some((base, mir::ProjectionElem::Index(_) | mir::ProjectionElem::ConstantIndex { .. })) => { + bytes(base.ty(&self.mir.local_decls, tcx).ty) + } + Some(_) => false, + }; + } + false + } + + /// The view of the byte the alias `local` names, if [`Self::byte_in_array`] found it in + /// an array of bytes for the call ending the block being lowered. + pub(super) fn byte_view(&self, local: Local) -> Option { + if !self.mirrors.views.contains(&self.at.block) { + return None; + } + let Slot::Alias(Some(target)) = &self.slots[local] else { + return None; + }; + let Expr::Index(bytes, at) = target.unlocated() else { + return None; + }; + let slice = Expr::New( + Box::new(Expr::Rs("ByteSlice".into())), + vec![(**bytes).clone(), (**at).clone(), Expr::lit("1")], + ); + Some(slice) + } + + /// The locals that may hold the slice the call assigning `made` makes, or what is made of + /// it; `None` if one may be kept where liveness can't follow it: in memory no local of + /// this body owns, a closure or coroutine's state, or what the function returns. A call + /// given one may put it in any result that could take a pointer, whatever its regions, + /// since unsafe code may make one up; in an argument's memory only where borrow checking + /// lets it, as [`Self::call_keeps`] reads that from its signature. + fn slice_holders(&self, made: mir::Place<'tcx>) -> Option> { + let mir = self.mir; + let tcx = self.tcx; + let memo = Default::default(); + let trace = Trace::new(self.b.t, mir, self.b.t.typing_env_pub(), &memo); + let mut held = DenseBitSet::new_empty(mir.local_decls.len()); + // Whether `place`, given one, is a newly found holder's: by a value read out of a + // holder, or with `taken`, by what a call returns or writes through one of its + // arguments, which it can make only into a value that could take it. + let keep = |held: &mut DenseBitSet, place: mir::Place<'tcx>, taken: bool| -> Option { + let ty = place.ty(&mir.local_decls, tcx).ty; + if !(match taken { + true => trace.takes_pointer(ty), + false => trace.carries(ty), + }) { + return Some(false); + } + let local = self.owner(place).filter(|&l| l != mir::RETURN_PLACE)?; + Some(held.insert(local)) + }; + held.insert(self.owner(made).filter(|&l| l != mir::RETURN_PLACE)?); + let mut grew = true; + while grew { + grew = false; + for data in mir.basic_blocks.iter() { + if data.is_cleanup { + continue; + } + for stmt in &data.statements { + match &stmt.kind { + mir::StatementKind::Assign(assign) => { + let (to, rvalue) = &**assign; + if !reads(&held, |v| v.visit_rvalue(rvalue, Location::START)) { + continue; + } + if let mir::Rvalue::Aggregate(kind, _) = rvalue + && matches!( + **kind, + mir::AggregateKind::Coroutine(..) | mir::AggregateKind::CoroutineClosure(..) + ) { + return None; + } + grew |= keep(&mut held, *to, false)?; + } + mir::StatementKind::Intrinsic(_) + if reads(&held, |v| v.visit_statement(stmt, Location::START)) => + { + return None; + } + _ => {} + } + } + let term = data.terminator(); + match &term.kind { + TerminatorKind::Call { + func, + args, + destination, + .. + } => { + let given: Vec = (0..args.len()) + .filter(|&i| args[i].node.place().is_some_and(|p| held.contains(p.local))) + .collect(); + if given.is_empty() && !func.place().is_some_and(|p| held.contains(p.local)) { + continue; + } + grew |= keep(&mut held, *destination, true)?; + for j in self.call_keeps(func, args.len(), &given, |ty| trace.hides_raw_pointer(ty)) { + // What the argument points at may be given one, if this body owns + // it; memory reached through more pointers can't be followed. + let ty = args[j].node.ty(&mir.local_decls, tcx); + match (args[j].node.place(), ty.builtin_deref(true)) { + (Some(p), Some(pointee)) if !trace.points_at_taker(pointee) => { + grew |= keep(&mut held, tcx.mk_place_deref(p), true)?; + } + _ if trace.points_at_taker(ty) => return None, + _ => {} + } + } + } + TerminatorKind::Yield { .. } + | TerminatorKind::TailCall { .. } + | TerminatorKind::InlineAsm { .. } + if reads(&held, |v| v.visit_terminator(term, Location::START)) => + { + return None; + } + _ => {} + } + } + } + Some(held) + } + + /// The local of this body's own that holds `place`: its local, or through a reference + /// made here once to a place of one, the local of that place; `None` for memory no local + /// of this body owns, as through a parameter. + fn owner(&self, place: mir::Place<'tcx>) -> Option { + let mut place = place; + for _ in 0..self.mir.local_decls.len() { + let Some(at) = place.projection.iter().position(|e| e == mir::ProjectionElem::Deref) else { + return Some(place.local); + }; + if at != 0 || self.defs[place.local] != 1 { + return None; + } + let (mir::Rvalue::Ref(_, _, to) | mir::Rvalue::RawPtr(_, to)) = self.def_rvalue(place.local)? else { + return None; + }; + place = to.project_deeper(&place.projection[1..], self.tcx); + } + None + } + + /// Whether block `bb` may run again after it runs, past cleanup blocks, which abort. + fn in_loop(&self, bb: BasicBlock) -> bool { + let blocks = &self.mir.basic_blocks; + let mut seen = DenseBitSet::new_empty(blocks.len()); + let mut stack: Vec<_> = blocks[bb].terminator().successors().collect(); + while let Some(block) = stack.pop() { + if block == bb { + return true; + } + if !blocks[block].is_cleanup && seen.insert(block) { + stack.extend(blocks[block].terminator().successors()); + } + } + false + } + + /// The arguments whose memory a call of `func`, given what may hold a slice in each + /// argument at `given`, may put it in, as its signature lets borrow checking allow. A + /// region the function's own signature binds, a late-bound one, is apart from every + /// other: the slice reaches only an argument whose type names one of the regions of an + /// argument holding it. A region or a type the caller picks may be shared with any + /// other, so then any argument may take it. A raw pointer made from the slice names no + /// region at all, so an argument pointing at memory `hides` says could hold one may + /// take it too. This trusts the callee's unsafe code not to make up a lifetime that + /// lets it keep the slice in an argument's memory, as it is trusted not to keep a raw + /// pointer to it where no argument points, such as in a thread-local. + fn call_keeps( + &self, + func: &Operand<'tcx>, + n: usize, + given: &[usize], + hides: impl Fn(Ty<'tcx>) -> bool, + ) -> Vec { + let tcx = self.tcx; + let everything = (0..n).filter(|j| !given.contains(j)).collect(); + let ty::FnDef(def_id, _) = *func.ty(&self.mir.local_decls, tcx).kind() else { + return everything; + }; + let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().skip_binder(); + let mut regions = Vec::new(); + for &i in given { + let Some(input) = sig.inputs().get(i) else { + return everything; + }; + for arg in input.walk() { + match arg.kind() { + ty::GenericArgKind::Lifetime(r) => match r.kind() { + ty::ReBound(ty::BoundVarIndexKind::Bound(ty::INNERMOST), br) => regions.push(br.var), + ty::ReStatic | ty::ReBound(..) => {} + _ => return everything, + }, + ty::GenericArgKind::Type(t) if matches!(t.kind(), ty::Param(_) | ty::Alias(..) | ty::Infer(_)) => { + return everything; + } + _ => {} + } + } + } + let names = |ty: Ty<'tcx>| { + ty.walk().any(|arg| { + matches!(arg.kind(), ty::GenericArgKind::Lifetime(r) + if matches!(r.kind(), ty::ReBound(ty::BoundVarIndexKind::Bound(ty::INNERMOST), br) if regions.contains(&br.var))) + }) + }; + (0..n) + .filter(|j| { + !given.contains(j) + && sig + .inputs() + .get(*j) + .is_none_or(|&t| names(t) || t.builtin_deref(true).is_some_and(&hides)) + }) + .collect() + } + + /// The slice of the byte at `p`, an `rs.Place`, that the call ending the block being + /// lowered makes, as its mirror's variable holds it. + pub(super) fn byte_slice_of(&mut self, p: Expr, span: rustc_span::Span) -> Expr { + match self.mirrors.made.get(&self.at.block) { + Some(Made::Var(name)) => Expr::ident(name).assign(Expr::rs("byteMirror", vec![p])).member("s"), + Some(Made::Unwritten) => Expr::rs("byteMirror", vec![p]).member("s"), + Some(&Made::Refused(why)) => self.err(span, why), + None => self.err(span, "a slice of one byte's place no call makes"), + } + } + + /// Write back the mirrors due as block `bb` starts. + pub(super) fn mirror_entry(&mut self, bb: BasicBlock, out: &mut Vec) { + let mut names: Vec = Vec::new(); + for (&from, due) in &self.mirrors.entry { + if self.forward(from) == bb { + names.extend(due.iter().filter(|n| !names.contains(n)).cloned().collect::>()); + } + } + names.sort(); + self.mirror_flush(names, out); + } + + /// Write back the mirrors due after the statement at `at`. + pub(super) fn mirror_after(&mut self, at: Location, out: &mut Vec) { + let names = self.mirrors.after.get(&at).cloned().unwrap_or_default(); + self.mirror_flush(names, out); + } + + fn mirror_flush(&mut self, names: Vec, out: &mut Vec) { + let stmts = names + .into_iter() + .map(|name| StmtKind::Expr(Expr::rs("byteMirrorFlush", vec![Expr::ident(&name)])).at(None)) + .collect::>(); + if !stmts.is_empty() { + self.emit(stmts, false, out); + } + } + + /// The variables of the body's mirrors. + pub(super) fn mirror_names(&self) -> Vec { + let mut names: Vec = (self.mirrors.made.values()) + .filter_map(|made| match made { + Made::Var(name) => Some(name.clone()), + _ => None, + }) + .collect(); + names.sort(); + names + } +} + +/// Whether what `visit` visits uses one of `held`, but to assign all of it. +fn reads(held: &DenseBitSet, visit: impl FnOnce(&mut Reads<'_>)) -> bool { + let mut reads = Reads { held, found: false }; + visit(&mut reads); + reads.found +} + +/// Finds a use of any of `held` but a whole assignment. +struct Reads<'h> { + held: &'h DenseBitSet, + found: bool, +} + +impl<'tcx> Visitor<'tcx> for Reads<'_> { + fn visit_local(&mut self, local: Local, cx: mir::visit::PlaceContext, _: Location) { + use mir::visit::{MutatingUseContext, PlaceContext}; + let stored = matches!( + cx, + PlaceContext::NonUse(_) | PlaceContext::MutatingUse(MutatingUseContext::Store | MutatingUseContext::Call) + ); + self.found |= !stored && self.held.contains(local); + } +} diff --git a/ts/src/lower/mir/mod.rs b/ts/src/lower/mir/mod.rs index 22015ea5e..1a83f7561 100644 --- a/ts/src/lower/mir/mod.rs +++ b/ts/src/lower/mir/mod.rs @@ -18,6 +18,7 @@ pub mod glue; pub(super) mod holders; mod inline; mod memory; +mod mirror; mod origin; mod place; mod rvalue; @@ -493,6 +494,8 @@ pub struct Fx<'m, 'b, 'a, 'tcx> { returning: FxHashSet, /// The tests and payload reads TypeScript may have narrowed stale. narrowing: decided::Narrowing<'tcx>, + /// The slices of one byte's place the body makes, as [`Self::find_mirrors`] finds them. + mirrors: mirror::Mirrors, /// The statement or terminator being lowered. at: mir::Location, } @@ -579,6 +582,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { slotted_calls: FxHashMap::default(), returning: FxHashSet::default(), narrowing, + mirrors: mirror::Mirrors::default(), at: mir::START_BLOCK.start_location(), }; fx.analyze(); @@ -844,6 +848,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } } self.share_stepped(); + self.find_mirrors(); } /// The byte locals whose `&raw` the body takes, of the local or of a reference to it @@ -861,9 +866,36 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.raw_bytes.insert(local); } } + // A slice of one of a `&mut` to it, which then views its buffer. + if let TerminatorKind::Call { func, args, .. } = &data.terminator().kind + && self.byte_slice_call(func) + && let Some(arg) = args[0].node.place() + && let Some(local) = self.lent_byte(arg) + { + self.raw_bytes.insert(local); + } } } + /// The byte local the reference `r` points at, if it was made here, once, as a `&mut` + /// to it or a reborrow of one, that nothing redirects. + fn lent_byte(&self, r: Place<'tcx>) -> Option { + let mut r = r.as_local()?; + for _ in 0..self.mir.local_decls.len() { + if self.defs[r] != 1 || self.is_arg(r) || self.mutated[r] { + return None; + } + match self.def_rvalue(r)? { + Rvalue::Ref(_, _, p) if p.projection.is_empty() => { + return crate::lower::byte(self.mir.local_decls[p.local].ty).then_some(p.local); + } + Rvalue::Ref(_, _, p) if p.projection.as_slice() == [mir::ProjectionElem::Deref] => r = p.local, + _ => return None, + } + } + None + } + /// The byte local `place`, the place of a `&raw`, is: the local, or what a reference /// to it made here, which nothing redirects through a `&mut` to it, points at. fn raw_byte(&self, place: Place<'tcx>) -> Option { @@ -1857,6 +1889,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { out.push(StmtKind::Declare { name, ty }.at(None)); } } + for name in self.mirror_names() { + let ty = Type::Union(vec![Type::Rs("ByteMirror", Vec::new()), Type::Builtin("undefined")]); + out.push(StmtKind::Declare { name, ty }.at(None)); + } self.scalar_decls(&mut out); let mut flags: Vec = self.range_flags.values().cloned().collect(); flags.sort(); @@ -2608,19 +2644,24 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { fn block(&mut self, bb: BasicBlock, out: &mut Vec) -> Exit { let mir = self.mir; let data = &mir.basic_blocks[bb]; + self.mirror_entry(bb, out); for (i, stmt) in data.statements.iter().enumerate() { + let at = mir::Location { + block: bb, + statement_index: i, + }; if !self.in_ignored(stmt.source_info.span) && !self.noops.contains(&(bb, i)) { - self.at = mir::Location { - block: bb, - statement_index: i, - }; + self.at = at; let start = out.len(); self.statement(stmt, out); self.locate(&mut out[start..]); + self.mirror_after(at, out); if self.returning.contains(&self.at) { self.flush(out); return Exit::End; } + } else { + self.mirror_after(at, out); } } if let Some(&to) = self.redirect.get(&bb) {