diff --git a/quest/m3/ts-runtime-throws.md b/quest/m3/ts-runtime-throws.md index 31a0d5c8c..f3aa2146c 100644 --- a/quest/m3/ts-runtime-throws.md +++ b/quest/m3/ts-runtime-throws.md @@ -28,10 +28,12 @@ JS keeps in two arrays, a pointer moved past the array JS keeps it in (`AtIndex. as one kept in a field), `Bytes::slice_ref` of other storage, `alloc`'s bytes read as values, ranges stepped without their type, fmt widths written as a placeholder, a read through a substring's `str::as_ptr` outside its own bytes but inside its text's -(`s[..1].as_ptr().add(1)`, `s[1..].as_ptr().wrapping_sub(1)`), and a raw -slice of a byte in no array cast to `*const [u8; N]` by a route the body doesn't show, -which [a byte local in a buffer of its own](/quest/m3/ts-unsafe/lone-byte-buffers.md) owns -for locals. +(`s[..1].as_ptr().add(1)`, `s[1..].as_ptr().wrapping_sub(1)`), and a raw or mutable slice +of a byte in a field (a struct's, a tuple's, a closure's capture, or one an async body keeps +across an `.await`), or a cast of one to `*const [u8; N]`: a field's byte has no buffer of +its own, unlike a `u8` local whose `&raw` the body takes, so the fix is a `Uint8Array(1)` +for each `u8` field whose address is taken. A `u8` local whose address only a call takes, +as `ptr::from_mut(&mut b)` does, has none either. The substring reads are UB under Stacked Borrows and allowed under Tree Borrows, so they throw until Rust settles its memory model (decided 2026-11-07 in [#634](https://github.com/moq-dev/rust2/pull/634)); if Rust rules them defined, a diff --git a/quest/m3/ts-unsafe/README.md b/quest/m3/ts-unsafe/README.md index d64a28b51..fdc0dd431 100644 --- a/quest/m3/ts-unsafe/README.md +++ b/quest/m3/ts-unsafe/README.md @@ -79,4 +79,3 @@ Public API: the translator's output. - [A raw pointer behind a reference](/quest/m3/ts-unsafe/raw-behind-ref.md) - `&raw const *r` of a value JS copies points where the reference came from - [NonZero's unchecked arithmetic](/quest/m3/ts-unsafe/nonzero-unchecked-ops.md) - `NonZero::unchecked_add` and `unchecked_mul` translate, aborting at std's site - [UnsafeCell's raw accessors](/quest/m3/ts-unsafe/unsafecell.md) - `UnsafeCell::raw_get`, `from_mut`, and `replace` translate -- [A byte local whose address is taken, in a buffer of its own](/quest/m3/ts-unsafe/lone-byte-buffers.md) - raw and mutable slices of a `u8` local, and casts of them to byte arrays, translate instead of being refused diff --git a/quest/m3/ts-unsafe/lone-byte-buffers.md b/quest/m3/ts-unsafe/lone-byte-buffers.md deleted file mode 100644 index 99d2157bf..000000000 --- a/quest/m3/ts-unsafe/lone-byte-buffers.md +++ /dev/null @@ -1,32 +0,0 @@ -# [M] A byte local whose address is taken, in a buffer of its own - -## Goal - -[rust2-ts](/ts/README.md) keeps a `u8` local whose raw pointer the body takes (`&raw const` -or `&raw mut` of it) in a `Uint8Array(1)`, so raw slices, mutable slices, and casts to -`*const [u8; N]` of it are views of that buffer, as for any other byte, proven by -differential tests against native Rust. Today JS keeps such a byte in no array of bytes, -so `stray` and `lone_byte_array` (`ts/src/lower/mir/origin.rs`) refuse, with `a raw or -mutable slice of a byte in no array of bytes`, each route the body shows, and the runtime -throws the same where it doesn't (`ptrByteSlice`, `RawSlice.byteArray` in -`ts/runtime/rs.ts`), or copies the byte for a shared slice (`Array::Copy`). - -## Plan - -Decided with the maintainer on 2026-11-07: m3. Planned on review of -[#624](https://github.com/moq-dev/rust2/pull/624), whose body-visible refusal of a cast of -an empty raw slice of a lone byte to `*const [u8; N]` kept missing routes (`&raw *s`, -`NonNull::from`, `NonNull::new(p).unwrap()`); this fixes the class at its root. - -- Which locals: those whose `&raw` the body takes, decided per body before lowering, so - every read and write of the local is `buf[0]` and a place of it is the `rs.AtIndex` of - element 0. -- Then the lone-byte refusals and the `Array::Copy` case go for those locals. A byte in a - field of an object JS keeps, or one behind a reference from elsewhere, stays in no array: - keep refusing or throwing there, and say so in the README's pointer row. - -Public API: the translator's output. - -## Related - -- [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md) - lists the run-time throws this removes for locals diff --git a/ts/README.md b/ts/README.md index 30baa5cdb..03cef191b 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: its items, the array `items`, and the capacity std's holds; `rs.vecFrom(items)` makes one of an array with room for exactly them. `rs.ByteVec` of bytes | | `Cow<[T]>`, `Cow` | `rs.Cow`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows, `rs.Cow>` holding the arrays of a slice of arrays; `rs.ByteCow` for bytes, an `rs.ByteVec` or an `rs.ByteSlice`; `rs.StrCow` for text, the `rs.String` it owns or an `rs.Borrowed` holding the string it borrows, whose text `rs.strCowStr` reads | | `&T`, `&mut T`, `Box`, `Reverse`, `NonZero`, `AssertUnwindSafe` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rs.Place`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. A pointer to a slice cast to one to an array of its elements is the `rs.Slice` or `rs.ByteSlice` of that many from its first, read through only where Rust reads. 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, 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 | diff --git a/ts/fixtures/lang/elements/expected/elements.ts b/ts/fixtures/lang/elements/expected/elements.ts index e4f4687a2..ede779512 100644 --- a/ts/fixtures/lang/elements/expected/elements.ts +++ b/ts/fixtures/lang/elements/expected/elements.ts @@ -209,6 +209,8 @@ export function inline(data: rs.ByteSlice, out: rs.ByteVec): void { * functions named as std's, and byte pointers into one buffer through its views. */ export function edges(data: rs.ByteSlice, out: rs.ByteVec): void { + const byte = new Uint8Array(1); + const y = new Uint8Array(1); let _200!: rs.Result<[rs.u32, rs.u32], rs.u8>; let _206!: rs.u8; for (const n of rs.byteSliceView(data)) { @@ -232,8 +234,8 @@ export function edges(data: rs.ByteSlice, out: rs.ByteVec): void { rs.byteVecPush(out, rs.ptrOffsetFrom(rs.ptrAdd(at, 1), at) & 0xff); rs.byteVecPush(out, rs.ptrOffsetFrom(rs.sliceAsPtr(rs.ptrSlice(at, 0)), at) & 0xff); rs.byteVecPush(out, lone & 0xff); - let byte: rs.u8 = n ^ 90; - const _97: rs.ByteSlice = rs.ptrByteSlice(rs.place(() => byte, (value2) => byte = value2), 1, true); + byte[0] = n ^ 90; + const _97: rs.ByteSlice = rs.ptrByteSlice(rs.arrayAsPtr(byte), 1, true); rs.idx(_97, 0); rs.byteVecPush(out, _97.data[_97.start]); const a: rs.u32[] = [n as rs.u32, 1, 2, 3]; @@ -259,9 +261,9 @@ export function edges(data: rs.ByteSlice, out: rs.ByteVec): void { rs.byteVecPush(out, _170.data[_170.start] & 0xff); const _177: [rs.u32, rs.u8] = [n as rs.u32, n]; let x: rs.u32 = _177[0]; - let y: rs.u8 = n; - rs.byteVecPush(out, offsetFrom(rs.place(() => x, (value3) => x = value3), rs.place(() => x, (value4) => x = value4)) & 0xff); - rs.byteVecPush(out, fromRawPartsMut(rs.place(() => y, (value5) => y = value5), 2)); + y[0] = n; + rs.byteVecPush(out, offsetFrom(rs.place(() => x, (value2) => x = value2), rs.place(() => x, (value3) => x = value3)) & 0xff); + rs.byteVecPush(out, fromRawPartsMut(rs.arrayAsPtr(y), 2)); const item = (undefined as unknown) as rs.Result<[rs.u32, rs.u32], rs.u8>; const item2 = (undefined as unknown) as rs.Result<[rs.u32, rs.u32], rs.u8>; const dst: rs.Result<[rs.u32, rs.u32], rs.u8>[] = Array.from({ length: 2 }, (): rs.Result<[rs.u32, rs.u32], rs.u8> => { @@ -275,7 +277,7 @@ export function edges(data: rs.ByteSlice, out: rs.ByteVec): void { } else { _200 = rs.Ok([x, 1] as [rs.u32, rs.u32]); } - const uninit = rs.place(() => dst[1], (value6) => dst[1] = value6); + const uninit = rs.place(() => dst[1], (value4) => dst[1] = value4); uninit.v = _200; uninit; const _207: rs.Result<[rs.u32, rs.u32], rs.u8> = dst[1]; @@ -432,6 +434,8 @@ export function rawSlices(data: rs.ByteSlice, out: rs.ByteVec): void { * part of which keeps the pointer as Rust's does: after moving back to address 0 it is null. */ export function emptyParts(data: rs.ByteSlice, out: rs.ByteVec): void { + const lone = new Uint8Array(1); + const held = new Uint8Array(1); const _130: (a0: rs.Place) => rs.u8 = (p2: rs.Place): rs.u8 => { return rs.ptrWrappingAdd(p2, -1, 1, 1) === rs.NULL ? 1 : 0; }; @@ -508,12 +512,12 @@ export function emptyParts(data: rs.ByteSlice, out: rs.ByteVec): void { rs.byteVecPush(out, rs.ptrWrappingAdd(raw.ptr, -k, 2, 2) === rs.NULL ? 1 : 0); const part22 = rs.ptrWrappingAdd(new rs.RawSlice(byte, 0).ptr, 0, 1, 1); rs.byteVecPush(out, _130(part22)); - let lone: rs.u8 = data.length & 0xff; - const p: rs.Place = rs.place(() => lone, (value) => lone = value); + lone[0] = data.length & 0xff; + const p: rs.Place = rs.arrayAsPtr(lone); rs.byteVecPush(out, rs.ptrOffsetFrom(rs.byteSliceAsPtr(rs.ptrByteSlice(p, 0, true)), p) & 0xff); rs.byteVecPush(out, rs.ptrOffsetFrom(rs.byteSliceAsPtr(rs.ptrByteSlice(rs.ptrAdd(p, 1), 0, true)), p) & 0xff); - let held: rs.u8 = lone; - const q: rs.Place = rs.place(() => held, (value2) => held = value2); + held[0] = lone[0]; + const q: rs.Place = rs.arrayAsPtr(held); const r: rs.ByteSlice = rs.ptrByteSlice(q, 0, false); rs.byteVecPush(out, rs.u8Add(r.length & 0xff, rs.ptrOffsetFrom(rs.byteSliceAsPtr(r), q) & 0xff)); } diff --git a/ts/fixtures/lang/pointers/expected/lib.ts b/ts/fixtures/lang/pointers/expected/lib.ts index e0bf9590c..519c4950b 100644 --- a/ts/fixtures/lang/pointers/expected/lib.ts +++ b/ts/fixtures/lang/pointers/expected/lib.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { lists, shared, places, unchecked, nested, copies, sentinels, cells, rebound, lapsed, writers, made, segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken, bytes, loaded, reattached, partWritten, aimed, distinct, rawArcs, rawPlaces, arrays, arrayCasts, genericUnions } from "./pointers.ts"; +import { lists, shared, places, unchecked, nested, copies, sentinels, cells, rebound, lapsed, writers, made, segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken, bytes, loaded, reattached, partWritten, aimed, distinct, rawArcs, rawPlaces, arrays, arrayCasts, genericUnions, loneBytes } from "./pointers.ts"; import type * as Aimed from "./pointers.Aimed.ts"; import * as Bin from "./pointers.Bin.ts"; import type * as ByCall from "./pointers.ByCall.ts"; @@ -306,6 +306,8 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By arrayCasts(data28, out28); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::generic_unions", (_238: rs.u8, data29: rs.ByteSlice, out29: rs.ByteVec): undefined => { genericUnions(data29, out29); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["pointers::lone_bytes", (_239: rs.u8, data30: rs.ByteSlice, out30: rs.ByteVec): undefined => { + loneBytes(data30, out30); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/pointers/expected/pointers.ts b/ts/fixtures/lang/pointers/expected/pointers.ts index 8c106dfcd..3f573a6dd 100644 --- a/ts/fixtures/lang/pointers/expected/pointers.ts +++ b/ts/fixtures/lang/pointers/expected/pointers.ts @@ -1440,6 +1440,7 @@ export function swapCell(c: rs.Cell, n: rs.u8): void { export function aimed(data: rs.ByteSlice, out: rs.ByteVec): void { let _17!: Paired.Paired | undefined; let _22!: To.To; + const num = new Uint8Array(1); let _43!: rs.Place | undefined; let _48!: To.To; let signed!: Leaning.Leaning; @@ -1522,8 +1523,8 @@ export function aimed(data: rs.ByteSlice, out: rs.ByteVec): void { swappedIn$pointers_Paired(pair, n, out); lend$pointers_Paired(pair, n, out); rs.byteVecPush(out, Paired.get$pointers_Aim(pair)); - let num: rs.u8 = n; - const at2: rs.Place = rs.place(() => num, (value) => num = value); + num[0] = n; + const at2: rs.Place = rs.arrayAsPtr(num); const pointer2 = at2; const held2: rs.Place = rs.optionUnwrap(pointer2 === rs.NULL ? undefined : pointer2); const _42: rs.Place = at2; @@ -1539,15 +1540,15 @@ export function aimed(data: rs.ByteSlice, out: rs.ByteVec): void { _48 = { tag: "At", _0: at2 } as To.To; } through$u8({ inner: { at: at2, seen: _42, held: held2, maybe: _43, tagged: _47, to: _48, read: readThrough$u8 as (a0: rs.Ptr) => rs.u8 } }, n, out); - swappedIn$u8(rs.place(() => num, (value2) => num = value2), n, out); - lend$u8(rs.place(() => num, (value3) => num = value3), n, out); - rs.byteVecPush(out, get$pointers_Aim_u8(num)); + swappedIn$u8(rs.arrayAsPtr(num), n, out); + lend$u8(rs.arrayAsPtr(num), n, out); + rs.byteVecPush(out, get$pointers_Aim_u8(num[0])); if (!(rs.u8Rem(n, 2) === 0)) { signed = { tag: "Neg", _0: n } as Leaning.Leaning; } else { signed = { tag: "Pos", _0: n } as Leaning.Leaning; } - const at3: rs.Place = rs.place(() => signed, (value4) => signed = value4); + const at3: rs.Place = rs.place(() => signed, (value) => signed = value); const pointer3 = at3; const held3: rs.Place = rs.optionUnwrap(pointer3 === rs.NULL ? undefined : pointer3); const _71: rs.Place = at3; @@ -1563,8 +1564,8 @@ export function aimed(data: rs.ByteSlice, out: rs.ByteVec): void { _77 = { tag: "At", _0: at3 } as To.To; } through$pointers_Leaning({ inner: { at: at3, seen: _71, held: held3, maybe: _72, tagged: _76, to: _77, read: readThrough$pointers_Leaning as (a0: rs.Ptr) => rs.u8 } }, n, out); - swappedIn$pointers_Leaning(rs.place(() => signed, (value5) => signed = value5), n, out); - lend$pointers_Leaning(rs.place(() => signed, (value6) => signed = value6), n, out); + swappedIn$pointers_Leaning(rs.place(() => signed, (value2) => signed = value2), n, out); + lend$pointers_Leaning(rs.place(() => signed, (value3) => signed = value3), n, out); rs.byteVecPush(out, Leaning.get$pointers_Aim(signed)); const bytes: Uint8Array = rs.bytes(4).fill(n); const at4: rs.ByteSlice = rs.byteSliceFrom(bytes); @@ -1588,7 +1589,7 @@ export function aimed(data: rs.ByteSlice, out: rs.ByteVec): void { const _116: rs.ByteSlice = rs.byteSliceFrom(bytes); rs.byteVecPush(out, get$pointers_Aim_array4_u8(_116.data, _116.start, _116.length)); let cell: rs.Cell = new rs.Cell(n); - const at5: rs.Place> = rs.place(() => cell, (value7) => cell = value7); + const at5: rs.Place> = rs.place(() => cell, (value4) => cell = value4); const pointer5 = at5; const held5: rs.Place> = rs.optionUnwrap(pointer5 === rs.NULL ? undefined : pointer5); const _123: rs.Place> = at5; @@ -1604,7 +1605,7 @@ export function aimed(data: rs.ByteSlice, out: rs.ByteVec): void { _129 = { tag: "At", _0: at5 } as To.To>; } through$Cell_u8({ inner: { at: at5, seen: _123, held: held5, maybe: _124, tagged: _128, to: _129, read: readThrough$Cell_u8 as (a0: rs.Ptr>) => rs.u8 } }, n, out); - swappedIn$Cell_u8(rs.place(() => cell, (value8) => cell = value8), n, out); + swappedIn$Cell_u8(rs.place(() => cell, (value5) => cell = value5), n, out); lend$Cell_u8(cell, n, out); rs.byteVecPush(out, cell.v); const _144: rs.u16 = ((n as rs.u16) << 4) & 0xffff; @@ -2989,6 +2990,7 @@ export function lent(n: rs.u8): rs.ByteVec { export function arrayCasts(data: rs.ByteSlice, out: rs.ByteVec): void { let _9!: rs.usize; let _91!: rs.usize; + const byte = new Uint8Array(1); const bytes: rs.ByteVec = rs.byteSliceToByteVec(data); const _6: rs.usize = bytes.length; let iterStart: rs.usize = 0; @@ -3056,12 +3058,13 @@ export function arrayCasts(data: rs.ByteSlice, out: rs.ByteVec): void { const part2 = bytes.length; rs.rawByteSliceOf(rs.byteSliceRange(rs.byteVecSlice(bytes), part2)); rs.byteVecPush(out, rs.byteSliceFrom(Uint8Array.of()).length & 0xff); - let byte: rs.u8 = option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 7); - const lone: rs.ByteSlice = new rs.RawSlice(rs.place(() => byte, (value) => byte = value), 0).byteArray(0); + const _187: rs.u8 | undefined = rs.byteSliceFirst(data); + byte[0] = option_unwrapOr$1fr498u(_187, 7); + const lone: rs.ByteSlice = new rs.RawSlice(rs.arrayAsPtr(byte), 0).byteArray(0); rs.byteVecPush(out, lone === rs.NULL ? 1 : 0); rs.byteVecPush(out, new rs.RawSlice(rs.byteSliceAsPtr(lone), 0).derefBytes(false).length & 0xff); - let word: rs.u32 = rs.u32Mul(byte as rs.u32, 3); - rs.iterExtendBytes(out, rs.iterSlice(rs.u32ToBeBytes(rs.sliceToArray(new rs.RawSlice(rs.place(() => word, (value2) => word = value2), 1).array(1))[0]).reverse()), undefined, true); + let word: rs.u32 = rs.u32Mul(byte[0] as rs.u32, 3); + rs.iterExtendBytes(out, rs.iterSlice(rs.u32ToBeBytes(rs.sliceToArray(new rs.RawSlice(rs.place(() => word, (value) => word = value), 1).array(1))[0]).reverse()), undefined, true); rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(new rs.RawSlice(rs.NULL, 4).byteArray(4) === rs.NULL ? 1 : 0, rs.sliceSame(new rs.RawSlice(rs.DANGLING, 0).array(2), rs.DANGLING) ? 1 : 0)), undefined, true); } @@ -3136,4 +3139,94 @@ export function genericUnions(data: rs.ByteSlice, out: rs.ByteVec): void { } } +/** + * Bytes whose `&raw` the body takes, a parameter's included: raw, mutable, and shared + * slices of each, and pointers to arrays cast from them by each route, are views of + * where the byte is, so a write through one reaches it and `offset_from` measures them. + */ +export function loneBytes(data: rs.ByteSlice, out: rs.ByteVec): void { + const b = new Uint8Array(1); + const c = new Uint8Array(1); + const m = new Uint8Array(1); + for (const d of rs.byteSliceView(data)) { + b[0] = d; + const raw: rs.RawSlice = new rs.RawSlice(rs.arrayAsPtr(b), 1); + rs.idx(raw, 0); + const slice = raw.derefBytes(false); + const slice2 = raw.derefBytes(false); + slice2.data[slice2.start] = slice.data[slice.start] ^ 90; + rs.byteVecPush(out, b[0]); + const s: rs.ByteSlice = rs.ptrByteSlice(rs.arrayAsPtr(b), 1, false); + rs.idx(s, 0); + const _19: rs.u8 = rs.u8WrappingAdd(s.data[s.start], 1); + rs.idx(s, 0); + s.data[s.start] = _19; + rs.byteVecPush(out, b[0]); + const _31: rs.u8 = rs.intRotateLeft(b[0], 3, 8, false); + b[0] = _31; + const shared: rs.ByteSlice = rs.ptrByteSlice(rs.arrayAsPtr(b), 1, true); + rs.idx(shared, 0); + rs.byteVecPush(out, shared.data[shared.start]); + rs.byteVecPush(out, rs.ptrOffsetFrom(rs.byteSliceAsPtr(shared), rs.arrayAsPtr(b)) & 0xff); + const one: rs.ByteSlice = new rs.RawSlice(rs.arrayAsPtr(b), 1).byteArray(1); + const _48: rs.u8 = rs.u8WrappingMul(one.data[one.start], 5); + one.data[one.start] = _48; + const none: rs.ByteSlice = new rs.RawSlice(rs.arrayAsPtr(b), 0).byteArray(1); + rs.byteVecPush(out, none.data[none.start]); + const empty: rs.ByteSlice = rs.ptrByteSlice(rs.arrayAsPtr(b), 0, true); + const lent: Uint8Array = rs.byteSliceToArray(rs.rawByteSliceOf(empty).byteArray(1)); + const from: Uint8Array = rs.byteSliceToArray(rs.rawByteSliceOf(empty).byteArray(1)); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(lent[0], from[0])), undefined, true); + const pointer = rs.arrayAsPtr(b); + const _83: rs.ByteSlice = new rs.RawSlice(rs.optionUnwrap(pointer === rs.NULL ? undefined : pointer), 1).byteArray(1); + _83.data[_83.start] = _83.data[_83.start] ^ 255; + const r: rs.Place = rs.arrayAsPtr(b); + r.v = rs.u8WrappingAdd(r.v, 7); + const _90: rs.Place = rs.arrayAsPtr(b); + ((by: rs.u8): undefined => { + _90.v = rs.u8WrappingAdd(_90.v, by); + })(d); + rs.byteVecPush(out, b[0]); + rs.byteVecPush(out, tripled(b[0])); + c[0] = d; + const cast: rs.ByteSlice = rs.ptrByteSlice(rs.arrayAsPtr(c), 1, false); + rs.idx(cast, 0); + const _103: rs.u8 = rs.u8WrappingSub(cast.data[cast.start], 9); + rs.idx(cast, 0); + cast.data[cast.start] = _103; + const _117: rs.ByteSlice = rs.ptrByteSlice(rs.arrayAsPtr(c), 1, true); + rs.idx(_117, 0); + rs.byteVecPush(out, _117.data[_117.start]); + m[0] = (0 as unknown) as rs.u8; + const _122: rs.u8 = c[0]; + const _124: rs.ByteSlice = rs.ptrByteSlice(rs.arrayAsPtr(m), 1, false); + rs.idx(_124, 0); + _124.data[_124.start] = _122; + rs.byteVecPush(out, m[0]); + const _135: [rs.u8, rs.u8] = [d, d ^ 1]; + let e: rs.u8 = d; + let f: rs.u8 = _135[1]; + let r2: rs.Place = rs.place(() => e, (value) => e = value); + redirect(rs.place(() => r2, (value2) => r2 = value2), rs.place(() => f, (value3) => f = value3)); + r2.v = 9; + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(e, f)), undefined, true); + } +} + +/** `r` pointed at `to`. */ +export function redirect(r: rs.Place>, to: rs.Place): void { + r.v = to; +} + +/** `b` tripled through a mutable slice of the parameter. */ +export function tripled(b: rs.u8): rs.u8 { + const b2 = Uint8Array.of(b); + const s: rs.ByteSlice = rs.ptrByteSlice(rs.arrayAsPtr(b2), 1, false); + rs.idx(s, 0); + const _4: rs.u8 = rs.u8WrappingMul(s.data[s.start], 3); + rs.idx(s, 0); + s.data[s.start] = _4; + return b2[0]; +} + //# sourceMappingURL=pointers.ts.map diff --git a/ts/fixtures/lang/pointers/src/lib.rs b/ts/fixtures/lang/pointers/src/lib.rs index a43a090d1..94c7f807f 100644 --- a/ts/fixtures/lang/pointers/src/lib.rs +++ b/ts/fixtures/lang/pointers/src/lib.rs @@ -53,5 +53,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("pointers::generic_unions", |_, data, out| { pointers::generic_unions(data, out) }), + ("pointers::lone_bytes", |_, data, out| pointers::lone_bytes(data, out)), ] } diff --git a/ts/fixtures/lang/pointers/src/pointers.rs b/ts/fixtures/lang/pointers/src/pointers.rs index f89770365..66c8ac8cd 100644 --- a/ts/fixtures/lang/pointers/src/pointers.rs +++ b/ts/fixtures/lang/pointers/src/pointers.rs @@ -2528,3 +2528,71 @@ pub fn generic_unions(data: &[u8], out: &mut Vec) { } } } + +/// Bytes whose `&raw` the body takes, a parameter's included: raw, mutable, and shared +/// slices of each, and pointers to arrays cast from them by each route, are views of +/// where the byte is, so a write through one reaches it and `offset_from` measures them. +pub fn lone_bytes(data: &[u8], out: &mut Vec) { + for &d in data { + let mut b = d; + let p = &raw mut b; + let raw = ptr::slice_from_raw_parts_mut(p, 1); + unsafe { (*raw)[0] ^= 0x5a }; + out.push(b); + let s = unsafe { std::slice::from_raw_parts_mut(&raw mut b, 1) }; + s[0] = s[0].wrapping_add(1); + out.push(b); + b = b.rotate_left(3); + let shared = unsafe { std::slice::from_raw_parts(&raw const b, 1) }; + out.push(shared[0]); + out.push(unsafe { shared.as_ptr().offset_from(&raw const b) } as u8); + // To an array, by a cast, `cast`, and from an empty slice of it, whose pointer still + // reaches the byte. + let one = ptr::slice_from_raw_parts_mut(&raw mut b, 1) as *mut [u8; 1]; + unsafe { (*one)[0] = (*one)[0].wrapping_mul(5) }; + let none = ptr::slice_from_raw_parts(&raw const b, 0).cast::<[u8; 1]>(); + out.push(unsafe { (*none)[0] }); + let empty = unsafe { std::slice::from_raw_parts(&raw const b, 0) }; + let lent = unsafe { *(&raw const *empty).cast::<[u8; 1]>() }; + let from = unsafe { *NonNull::from(empty).cast::<[u8; 1]>().as_ptr() }; + out.extend([lent[0], from[0]]); + let nn = NonNull::new(&raw mut b).unwrap(); + let made = NonNull::slice_from_raw_parts(nn, 1).cast::<[u8; 1]>(); + unsafe { (*made.as_ptr())[0] ^= 0xff }; + // A `&mut` to it and a closure's are where its pointers are. + let r = &mut b; + *r = r.wrapping_add(7); + let mut bump = |by: u8| b = b.wrapping_add(by); + bump(d); + out.push(b); + out.push(tripled(b)); + // Through a pointer cast from a `&mut`, and a `MaybeUninit` byte's. + let mut c = d; + let cast = unsafe { std::slice::from_raw_parts_mut(&mut c as *mut u8, 1) }; + cast[0] = cast[0].wrapping_sub(9); + let p: *const u8 = &c; + out.push(unsafe { std::slice::from_raw_parts(p, 1)[0] }); + let mut m = MaybeUninit::::uninit(); + unsafe { std::slice::from_raw_parts_mut(&raw mut m, 1)[0] = MaybeUninit::new(c) }; + out.push(unsafe { m.assume_init() }); + // Through a reference redirected by a `&mut` to it, at the byte it now points at. + let (mut e, mut f) = (d, d ^ 1); + let mut r = &mut e; + redirect(&mut r, &mut f); + let p = &raw mut *r; + unsafe { *p = 9 }; + out.extend([e, f]); + } +} + +/// `r` pointed at `to`. +fn redirect<'a>(r: &mut &'a mut u8, to: &'a mut u8) { + *r = to; +} + +/// `b` tripled through a mutable slice of the parameter. +fn tripled(mut b: u8) -> u8 { + let s = unsafe { std::slice::from_raw_parts_mut(&raw mut b, 1) }; + s[0] = s[0].wrapping_mul(3); + b +} diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 155cd4036..5dbe12ffc 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -189,13 +189,6 @@ fixtures/reject/src/pointers.rs:erased_empty+2:20: a pointer to `((), ())` cast fixtures/reject/src/pointers.rs:erased_place+2:2: a pointer to `u32` cast to one to `()`, whose JS value is a place of its own each borrow fixtures/reject/src/pointers.rs:erased_place+2:22: a pointer to `u32` cast to one to `()`, whose JS value is a place of its own each borrow fixtures/reject/src/pointers.rs:erased_start+2:2: a pointer to `pointers::Outer` cast to one to `()`, whose address its `pointers::Inner` at its start shares, which JS keeps as an object of its own -fixtures/reject/src/pointers.rs:lone_byte_arrays+2:13: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_arrays+3:17: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_arrays+5:13: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_arrays+6:39: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_arrays+7:44: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_slices+1:12: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_slices+2:19: a raw or mutable slice of a byte in no array of bytes fixtures/reject/src/pointers.rs:made_part_replaced+5:10: a raw pointer to `(u32, u32)` in `pointers::Gap`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:made_part_replaced+6:10: a raw pointer to `(u32, u32)` in `pointers::Gap`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:made_part_replaced+7:58: a raw pointer to `(u32, u32)` in `pointers::Gap`, which the crate may put another object in place of @@ -218,7 +211,6 @@ fixtures/reject/src/pointers.rs:pointers_apart+9:5: `offset_from` between a poin fixtures/reject/src/pointers.rs:pointers_apart+10:26: `offset_from` between a pointer into a value in no array and one into a value in no array, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_apart+11:24: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_apart+12:23: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays -fixtures/reject/src/pointers.rs:pointers_apart+14:21: `offset_from` between a pointer into a shared slice JS copies and one into a value in no array, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_ordered+2:2: comparing pointers to `u32`, each a place JS makes of its own fixtures/reject/src/pointers.rs:pointers_ordered+2:2: ordering `*const u32` by its address, which JS has no counterpart of, in `std::cmp::max` fixtures/reject/src/pointers.rs:put_replaced+2:10: a raw pointer to `(u32, u32)` in `pointers::Plain`, which the crate may put another object in place of diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 502204dee..04cd4ee91 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -191,13 +191,6 @@ fixtures/reject/src/pointers.rs:erased_empty+2:20: a pointer to `((), ())` cast fixtures/reject/src/pointers.rs:erased_place+2:2: a pointer to `u32` cast to one to `()`, whose JS value is a place of its own each borrow fixtures/reject/src/pointers.rs:erased_place+2:22: a pointer to `u32` cast to one to `()`, whose JS value is a place of its own each borrow fixtures/reject/src/pointers.rs:erased_start+2:2: a pointer to `pointers::Outer` cast to one to `()`, whose address its `pointers::Inner` at its start shares, which JS keeps as an object of its own -fixtures/reject/src/pointers.rs:lone_byte_arrays+2:13: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_arrays+3:17: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_arrays+5:13: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_arrays+6:39: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_arrays+7:44: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_slices+1:12: a raw or mutable slice of a byte in no array of bytes -fixtures/reject/src/pointers.rs:lone_byte_slices+2:19: a raw or mutable slice of a byte in no array of bytes fixtures/reject/src/pointers.rs:made_part_replaced+5:10: a raw pointer to `(u32, u32)` in `pointers::Gap`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:made_part_replaced+6:10: a raw pointer to `(u32, u32)` in `pointers::Gap`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:made_part_replaced+7:58: a raw pointer to `(u32, u32)` in `pointers::Gap`, which the crate may put another object in place of @@ -220,7 +213,6 @@ fixtures/reject/src/pointers.rs:pointers_apart+9:5: `offset_from` between a poin fixtures/reject/src/pointers.rs:pointers_apart+10:26: `offset_from` between a pointer into a value in no array and one into a value in no array, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_apart+11:24: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_apart+12:23: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays -fixtures/reject/src/pointers.rs:pointers_apart+14:21: `offset_from` between a pointer into a shared slice JS copies and one into a value in no array, which JS keeps in different arrays fixtures/reject/src/pointers.rs:pointers_ordered+2:2: comparing pointers to `u32`, each a place JS makes of its own fixtures/reject/src/pointers.rs:pointers_ordered+2:2: ordering `*const u32` by its address, which JS has no counterpart of, in `std::cmp::max` fixtures/reject/src/pointers.rs:put_replaced+2:10: a raw pointer to `(u32, u32)` in `pointers::Plain`, which the crate may put another object in place of diff --git a/ts/fixtures/reject/src/pointers.rs b/ts/fixtures/reject/src/pointers.rs index d81ff2fe0..9e9cc2a78 100644 --- a/ts/fixtures/reject/src/pointers.rs +++ b/ts/fixtures/reject/src/pointers.rs @@ -420,27 +420,6 @@ pub fn text_before(s: &str) -> u8 { unsafe { *s[1..].as_ptr().sub(1) } } -/// A raw and a mutable slice of a byte in no array, which no `Uint8Array` can share. -pub fn lone_byte_slices(mut b: u8) -> u8 { - let raw = std::ptr::slice_from_raw_parts_mut(&raw mut b, 1); - let s = unsafe { std::slice::from_raw_parts_mut(&raw mut b, 1) }; - s[0] = 1; - unsafe { (*raw)[0] } -} - -/// None of a byte in no array, cast to a pointer to bytes from it, which no `Uint8Array` -/// can share, though Rust reads nothing at the cast. -pub fn lone_byte_arrays(b: u8) -> bool { - let none = std::ptr::slice_from_raw_parts(&raw const b, 0); - let pair = none as *const [u8; 2]; - let one = none.cast::<[u8; 1]>(); - let empty = unsafe { std::slice::from_raw_parts(&raw const b, 0) }; - let lent = empty as *const [u8] as *const [u8; 1]; - let from = std::ptr::from_ref(empty).cast::<[u8; 1]>(); - let made = std::ptr::NonNull::from(empty).cast::<[u8; 1]>(); - pair.is_null() || one.is_null() || lent.is_null() || from.is_null() || made.as_ptr().is_null() -} - /// Pointers, slices, and copies the body shows reach out of an array of one or a copied /// subslice, where Rust's reach the rest of what holds them. pub fn reached_out(h: Halves, lanes: [u32; 4]) -> u32 { diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index c345424cd..ae956cc1c 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -144,8 +144,6 @@ roots = [ "pointers::erased_empty", "pointers::erased_place", "pointers::erased_start", - "pointers::lone_byte_arrays", - "pointers::lone_byte_slices", "pointers::made_part_replaced", "pointers::nonnull_reassigned", "pointers::object_elements", diff --git a/ts/src/lower/mir/mod.rs b/ts/src/lower/mir/mod.rs index 8af131767..87a9bf89c 100644 --- a/ts/src/lower/mir/mod.rs +++ b/ts/src/lower/mir/mod.rs @@ -308,6 +308,10 @@ enum Slot<'tcx> { Unit, /// A JS variable. Var(String), + /// A byte whose `&raw` the body takes: element 0 of a `Uint8Array` of its own in this + /// variable, so a raw or mutable slice of it, and an array cast from one, is a view of + /// its buffer, as of any other byte in an array of bytes. + Byte(String), /// A reference assigned once, read as the place it borrows, which is set once its /// assignment is lowered. Alias(Option), @@ -367,6 +371,11 @@ pub struct Fx<'m, 'b, 'a, 'tcx> { mutated: IndexVec, /// The names of borrowed locals. shared: FxHashSet, + /// Byte locals whose `&raw` the body takes, which [`Slot::Byte`] holds, as + /// [`Self::find_raw_bytes`] finds them. + raw_bytes: FxHashSet, + /// The name each parameter held as a [`Slot::Byte`] arrives by. + byte_params: FxHashMap, /// The names of locals whose own value nothing changes: a tuple, array, or enum or /// struct of their own, never changed in place nor borrowed, so a call can't change /// what their fields hold. @@ -500,6 +509,8 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { borrowed: IndexVec::from_elem_n(false, n), mutated: IndexVec::from_elem_n(false, n), shared: FxHashSet::default(), + raw_bytes: FxHashSet::default(), + byte_params: FxHashMap::default(), frozen_names: FxHashSet::default(), checked: FxHashSet::default(), pending: Vec::new(), @@ -710,6 +721,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.operand_use(len); self.operand_use(index); } + self.find_raw_bytes(); self.drop_unread(); self.find_next_results(); self.find_stepped(); @@ -724,6 +736,8 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Slot::Unit } else if self.scalars.contains_key(&local) { Slot::Var(String::new()) + } else if self.raw_bytes.contains(&local) && local != mir::RETURN_PLACE { + Slot::Byte(String::new()) } else if let Some(value) = self.unassigned(local, ty) { Slot::Alias(Some(value)) } else if self.alias(local) && !self.reads_scalar_object(local) { @@ -759,6 +773,20 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } } for local in mir.local_decls.indices() { + if let Slot::Byte(_) = self.slots[local] { + let base = match named.get(&local) { + Some(name) => super::camel(name.as_str()), + None => format!("_{}", local.as_u32()), + }; + // A parameter keeps its name in the signature, and its buffer takes another. + if self.is_arg(local) { + let param = self.b.fresh(&base); + self.byte_params.insert(local, param); + } + let name = self.b.fresh(&base); + self.shared.insert(name.clone()); + self.slots[local] = Slot::Byte(name); + } if let Slot::Var(_) = self.slots[local] { let base = match named.get(&local) { Some(name) => super::camel(name.as_str()), @@ -794,6 +822,42 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.share_stepped(); } + /// The byte locals whose `&raw` the body takes, of the local or of a reference to it + /// made here, as `&mut b as *mut u8` is. + fn find_raw_bytes(&mut self) { + for data in self.mir.basic_blocks.iter() { + for stmt in &data.statements { + let StatementKind::Assign(assign) = &stmt.kind else { + continue; + }; + let Rvalue::RawPtr(mir::RawPtrKind::Const | mir::RawPtrKind::Mut, place) = &assign.1 else { + continue; + }; + if let Some(local) = self.raw_byte(*place) { + self.raw_bytes.insert(local); + } + } + } + } + + /// 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 { + let local = match place.projection.as_slice() { + [] => place.local, + [mir::ProjectionElem::Deref] + if self.defs[place.local] == 1 && !self.is_arg(place.local) && !self.mutated[place.local] => + { + match self.def_rvalue(place.local)? { + Rvalue::Ref(_, _, p) if p.projection.is_empty() => p.local, + _ => return None, + } + } + _ => return None, + }; + crate::lower::byte(self.mir.local_decls[local].ty).then_some(local) + } + /// Uncount the reads of what computes a local nothing reads without doing anything /// else, as the test of an assert a runtime function checks for itself is. fn drop_unread(&mut self) { @@ -1612,6 +1676,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { }; let name = match &self.slots[local] { Slot::Var(name) => name.clone(), + Slot::Byte(_) => self.byte_params[&local].clone(), _ => self.b.fresh("_"), }; if self.parted.contains(&local) { @@ -1635,6 +1700,11 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { entry.push(StmtKind::decl(self.defs[local] > 0, name, Some(ty), Some(slice)).at(None)); } for local in self.mir.args_iter() { + if let Slot::Byte(name) = &self.slots[local] { + let param = Expr::ident(&self.byte_params[&local]); + let buffer = Expr::Member(Box::new(Expr::ident("Uint8Array")), crate::ts::Key::Name("of".into())); + entry.push(StmtKind::decl(false, name.clone(), None, Some(buffer.call(vec![param]))).at(None)); + } let ty = self.mir.local_decls[local].ty; if self.mutated[local] && let Slot::Var(name) = &self.slots[local] @@ -1688,6 +1758,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.labels = labels; let mut out = Vec::new(); for local in self.mir.local_decls.indices() { + if let Slot::Byte(name) = &self.slots[local] + && !self.is_arg(local) + { + let buffer = Expr::New(Box::new(Expr::ident("Uint8Array")), vec![Expr::lit("1")]); + out.push(StmtKind::decl(false, name.clone(), None, Some(buffer)).at(None)); + continue; + } // Parameters are declared by the signature. if self.is_arg(local) || !self.assigned[local] { continue; @@ -2435,6 +2512,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.var_parts.insert(local, parts); } } + Slot::Byte(name) => { + let stmt = StmtKind::Expr(Expr::ident(name).index(Expr::lit("0")).assign(value)).at(loc); + self.emit(vec![stmt], false, out); + } Slot::Unit => { if effect { self.emit(vec![StmtKind::Expr(value).at(loc)], false, out); diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index b0b4a33f1..2b59e635c 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -12,7 +12,7 @@ use rustc_middle::mir::{self, BasicBlock, Local, Location, Operand, ProjectionEl use rustc_middle::ty::{self, GenericArgsRef}; use rustc_span::Spanned; -use super::Fx; +use super::{Fx, Slot}; use crate::lower::library::{self, Std}; use crate::lower::pointer::{self, Pointee}; use crate::ts::{Expr, Stmt}; @@ -22,6 +22,8 @@ use crate::ts::{Expr, Stmt}; enum Array<'tcx> { /// An array of one, around the place a `&raw` here took, which each `&raw` makes anew. Lone(Location), + /// The `Uint8Array` of one a byte variable, whose `&raw` the body takes, is kept in. + Byte(Local), /// A shared slice JS copies, of a byte in no array, made by the call ending this block, /// with its length if the body fixes it. Copy(BasicBlock, Option), @@ -35,7 +37,7 @@ impl<'tcx> Array<'tcx> { /// How many elements the array holds, if the body shows it. fn len(self) -> Option { match self { - Array::Lone(_) => Some(1), + Array::Lone(_) | Array::Byte(_) => Some(1), Array::Copy(_, len) => len, Array::Owned(_) | Array::Text(_) => None, } @@ -44,7 +46,7 @@ impl<'tcx> Array<'tcx> { /// What the array is, as a refusal names it. fn what(self) -> &'static str { match self { - Array::Lone(_) => "a value in no array", + Array::Lone(_) | Array::Byte(_) => "a value in no array", Array::Copy(..) => "a shared slice JS copies", Array::Owned(_) => "an array of this body's own", Array::Text(_) => "a `str`'s bytes", @@ -61,9 +63,8 @@ struct Origin<'tcx> { impl<'tcx> Fx<'_, '_, '_, 'tcx> { /// Why the call of std's `def_id` would fail at run time where Rust's is defined, if the - /// body proves it would: `offset_from` between pointers JS keeps in different arrays, a - /// raw or mutable slice of a byte in no array of bytes, or a pointer, slice, or copy - /// reaching out of an array of one or a copied byte. + /// body proves it would: `offset_from` between pointers JS keeps in different arrays, or + /// a pointer, slice, copy, or write reaching out of an array of one or a copied byte. pub(super) fn stray( &self, def_id: rustc_hir::def_id::DefId, @@ -76,11 +77,6 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { let (owner, name) = library::key(self.tcx, def_id)?; let arg = |i: usize| args.get(i).map(|a| &a.node); let pointee = gargs.types().next()?; - if matches!((owner.as_str(), name.as_str()), ("ptr" | "NonNull", "cast")) - && let Some(why) = self.lone_byte_array(arg(0)?, pointee, gargs.types().nth(1)?) - { - return Some(why); - } if self.b.t.zero_sized(pointee) || self.b.t.pointee(pointee) != Pointee::Place { return None; } @@ -134,11 +130,6 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { | ("ptr", "slice_from_raw_parts" | "slice_from_raw_parts_mut") | ("NonNull", "slice_from_raw_parts") => { let o = self.origin(arg(0)?)?; - let byte = crate::lower::byte(pointee); - let none = self.const_int(arg(1)?) == Some(0); - if byte && name != "from_raw_parts" && !none && matches!(o.array, Array::Lone(_)) { - return Some("a raw or mutable slice of a byte in no array of bytes".into()); - } // A raw pointer to a slice reads nothing until a read through it. if owner != "raw" { return None; @@ -208,6 +199,15 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { op, _, ) if self.same_pointee(op, local) => self.origin(op), + Rvalue::RawPtr(mir::RawPtrKind::Const | mir::RawPtrKind::Mut, place) + if let Some(local) = self.raw_byte(*place) + && matches!(self.slots[local], Slot::Byte(_)) => + { + Some(Origin { + array: Array::Byte(local), + index: Some(0), + }) + } Rvalue::RawPtr(mir::RawPtrKind::Const | mir::RawPtrKind::Mut, place) => { match place.projection.as_slice() { // `&raw mut *p` is `p`. @@ -448,54 +448,6 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { .then(|| pointer::erased_place(t.uninit_inner(from))) } - /// Why the cast of the raw slice `op` of bytes, a pointer to `from`, to one to `to`, an - /// array of some of them, would fail at run time where Rust's is defined: the body shows - /// the slice starts at a byte in no array, which no `Uint8Array` can share. - pub(super) fn lone_byte_array(&self, op: &Operand<'tcx>, from: ty::Ty<'tcx>, to: ty::Ty<'tcx>) -> Option { - let t = &self.b.t; - let (ty::Slice(from), ty::Array(to, n)) = (t.uninit_inner(from).kind(), t.uninit_inner(to).kind()) else { - return None; - }; - if !crate::lower::byte(*from) || !crate::lower::byte(*to) || n.try_to_target_usize(self.tcx) == Some(0) { - return None; - } - matches!(self.raw_start(op)?.array, Array::Lone(_)) - .then(|| "a raw or mutable slice of a byte in no array of bytes".into()) - } - - /// Where the raw slice, `NonNull` of one, or slice `op` starts, if the body shows it. - fn raw_start(&self, op: &Operand<'tcx>) -> Option> { - let local = self.traced(op)?; - if self.def_at[local].is_some() { - return match self.def_rvalue(local)? { - Rvalue::Use(op, _) - | Rvalue::Cast( - mir::CastKind::PtrToPtr - | mir::CastKind::PointerCoercion(ty::adjustment::PointerCoercion::MutToConstPointer, _), - op, - _, - ) if self.same_pointee(op, local) => self.raw_start(op), - // `&raw const *s`, of the slice `s`. - Rvalue::RawPtr(_, place) if place.projection.as_slice() == [ProjectionElem::Deref] => { - self.raw_start(&Operand::Copy(mir::Place::from(place.local))) - } - _ => self.start(op), - }; - } - let (def_id, gargs, args) = self.def_call(local)?; - // A pointer or reference to where its first argument points, as a whole. - if pointer::from_reference(self.tcx, def_id, gargs) - || pointer::derives(self.tcx, def_id) == Some(pointer::Derives::Same) - { - return self.raw_start(&args.first()?.node); - } - let (owner, name) = library::key(self.tcx, def_id)?; - match (owner.as_str(), name.as_str()) { - ("ptr" | "NonNull", "slice_from_raw_parts" | "slice_from_raw_parts_mut") => self.origin(&args[0].node), - _ => self.start(op), - } - } - /// The local `op` reads whole, if the body assigns it once. pub(super) fn traced(&self, op: &Operand<'tcx>) -> Option { let local = op.place()?.as_local()?; diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 3516596bc..7818dca0b 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -37,6 +37,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } match &self.slots[local] { Slot::Var(name) => Expr::ident(name), + Slot::Byte(name) => Expr::ident(name).index(Expr::lit("0")), // A value of type `!`, as a generic instantiated at `!` reads: native code never // gets here, and `tsc` sees that through `rs.unreachable`. Slot::Unit if self.mir.local_decls[local].ty.is_never() => Expr::rs("unreachable", vec![Expr::undefined()]), @@ -792,10 +793,12 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let Expr::Ident(name) = e else { return false; }; - self.local_named(name).is_some_and(|local| { - let assigned = self.defs[local] + u32::from(self.is_arg(local)); - assigned <= 1 && !self.mutated[local] - }) + // A byte's buffer is the same one throughout. + self.byte_named(name) + || self.local_named(name).is_some_and(|local| { + let assigned = self.defs[local] + u32::from(self.is_arg(local)); + assigned <= 1 && !self.mutated[local] + }) } /// Whether `place` is part of a local's own value that nothing changes in place. @@ -1197,6 +1200,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { /// The `rs.Place` that reads and writes `target`, a path that stays put, read as /// `payload` if it is a variant's. pub(super) fn place_of(&mut self, target: Expr, payload: Option) -> Expr { + if let Some(p) = self.byte_place(&target) { + return p; + } let value = self.b.fresh("value"); let param = crate::ts::Param { name: value.clone(), @@ -1221,6 +1227,24 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Expr::rs("place", vec![get, set]) } + /// The pointer to the byte `target` reads, if it is a [`Slot::Byte`]'s: element 0 of its + /// buffer, the same each time it is taken. + fn byte_place(&self, target: &Expr) -> Option { + let Expr::Index(buffer, index) = target.unlocated() else { + return None; + }; + let Expr::Ident(name) = buffer.unlocated() else { + return None; + }; + (self.byte_named(name) && matches!(index.unlocated(), Expr::Lit(i) if i == "0")) + .then(|| Expr::rs("arrayAsPtr", vec![Expr::ident(name)])) + } + + /// Whether `name` is a [`Slot::Byte`]'s buffer. + fn byte_named(&self, name: &str) -> bool { + self.slots.iter().any(|slot| matches!(slot, Slot::Byte(n) if n == name)) + } + /// The `rs.Place` of `place`, a reborrow `&mut *r` of a reference or pointer `r` that is /// one, which is `r`'s. A shared reborrow `&*r` is what `r` points at instead, and so is /// a `&mut` to an object that a pointer to is a place, such as a `Cell`. diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 8c20fb57e..3446ee3c4 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -130,6 +130,12 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // `&raw mut *p` is `p`. A pointer to `()` is an identity, which only another // pointer is: not the `rs.Place` of a place or of a `&mut`, each borrow's own. Pointee::Place => match place.projection.as_slice() { + // A byte in a buffer of its own: element 0 of it. + _ if let Some(local) = self.raw_byte(*place) + && let Slot::Byte(name) = &self.slots[local] => + { + Expr::rs("arrayAsPtr", vec![Expr::ident(name)]) + } [rest @ .., mir::ProjectionElem::Deref] if !self.box_pointer(place.local) && { let base = Place::ty_from(place.local, rest, &self.mir.local_decls, tcx).ty; @@ -485,7 +491,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { if let (Some(a), Some(b)) = (from.builtin_deref(true), to.builtin_deref(true)) && from.is_raw_ptr() => { - if let Some(why) = self.erased_borrow(op, a, b).or_else(|| self.lone_byte_array(op, a, b)) { + if let Some(why) = self.erased_borrow(op, a, b) { return self.err(span, why); } if let Some(cast) = self.alloc_cast(op, a, b, span, out) {