From dd2ebf5c472ac1381051ebe210b52efa0e6b1e53 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 10:16:40 -0800 Subject: [PATCH 01/34] chore(quest): claim quest/m3/ts-unsafe.md Quest-Claim: c56ea58c47a4ebd5 From 3bafa03fdc87abb4c2941e1c0f6aabe577e2fca2 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 11:05:48 -0800 Subject: [PATCH 02/34] feat(ts): translate pointers into arrays as rt.AtIndex Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-unsafe.md | 15 +- ts/README.md | 8 +- ts/fixtures/crates/dep/src/lib.rs | 2 +- ts/fixtures/crates/refused.txt | 3 +- ts/fixtures/lang/expected/core/bool.ts | 7 + ts/fixtures/lang/expected/core/option.ts | 14 +- ts/fixtures/lang/expected/elements.Inline.ts | 108 ++++++++ ts/fixtures/lang/expected/elements.ts | 207 +++++++++++++++ ts/fixtures/lang/expected/harness.ts | 15 +- ts/fixtures/lang/expected/lib.ts | 27 ++ ts/fixtures/lang/expected/tuples.ts | 2 +- ts/fixtures/lang/src/elements.rs | 256 +++++++++++++++++++ ts/fixtures/lang/src/harness.rs | 12 +- ts/fixtures/lang/src/lib.rs | 4 +- ts/fixtures/reject/census.md | 115 ++++++--- ts/fixtures/reject/expected.txt | 19 +- ts/fixtures/reject/src/lib.rs | 28 +- ts/fixtures/reject/ts.toml | 3 + ts/runtime/rt.ts | 72 ++++++ ts/runtime/rt/collections.ts | 20 +- ts/src/lower/copy.rs | 7 +- ts/src/lower/mir/rvalue.rs | 25 +- ts/src/lower/pointer.rs | 155 ++++++++++- 23 files changed, 1034 insertions(+), 90 deletions(-) create mode 100644 ts/fixtures/lang/expected/elements.Inline.ts create mode 100644 ts/fixtures/lang/expected/elements.ts create mode 100644 ts/fixtures/lang/src/elements.rs diff --git a/quest/m3/ts-unsafe.md b/quest/m3/ts-unsafe.md index 286651d7c..b4722439c 100644 --- a/quest/m3/ts-unsafe.md +++ b/quest/m3/ts-unsafe.md @@ -4,8 +4,9 @@ [rust2-ts](/ts/README.md) translates the `unsafe` operations it can give exact semantics in JS, so a dependency using only those translates from source instead of being bound by -hand. What is left: pointers into a slice or `Vec`, `ManuallyDrop`, unions over a type -parameter, and a raw pointer to a value JS copies taken through a reference. +hand. What is left: `ManuallyDrop`, unions over a type parameter, a raw pointer to a +value JS copies taken through a reference, and pointers into an array of values JS keeps +as objects. ## Plan @@ -25,8 +26,14 @@ none of its own `unsafe`. - An `unsafe` operation outside the modeled set is refused with its span, as today. - Undefined behavior in Rust need not be reproduced, but a translated program never silently differs from native where Rust's behavior is defined. -- Element pointers, `{ array, index }`, with `add`, `offset`, `offset_from`, and - `slice::from_raw_parts` over them: smallvec, arrayvec, and moq-net's `frame.rs`. +- Element pointers into arrays of values JS copies are `rt.AtIndex`, `{ array, index }`, + which `add`, `offset`, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` act on. + A pointer to an element JS keeps as an object, such as a tuple or the crate's struct, is + that object, which knows no array it is in, so arithmetic on one stays refused: + hashbrown's buckets and kio's `log.rs` reach it. smallvec's pointer arithmetic + translates, but it spills to memory from `alloc::alloc`, which this Plan refuses, so it + translates whole only once the maintainer decides how a typed allocation + (`Layout::array::`, then a cast to `*mut T`) is modeled or that smallvec is mapped. - `ManuallyDrop` as `T` with its drop skipped, as `MaybeUninit` is: crossbeam-epoch's `Local` reaches it, though crossbeam's tagged pointers stay refused. - A union over a type parameter, whose layout has no fixed offsets until an instance diff --git a/ts/README.md b/ts/README.md index 52da3d1c6..7e139703e 100644 --- a/ts/README.md +++ b/ts/README.md @@ -101,7 +101,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | tuple, `[T; N]`, `&[T]`, `Box<[T]>`, `Vec` | an array; for bytes, `Uint8Array` for `[u8; N]`, `rt.ByteSlice` for `&[u8]`, `&[u8; N]`, and `Box<[u8]>`, and `rt.ByteVec` for `Vec` | | `Cow<[T]>` | `rt.Cow`: the `Vec` it owns, or an `rt.Borrowed` holding the slice it borrows; `rt.ByteCow` for bytes, an `rt.ByteVec` or an `rt.ByteSlice` | | `&T`, `&mut T`, `Box`, `Reverse`, `NonZero` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, a `String`, or a reference, or to an `Arc` or a `Waker`, is an `rt.Place`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | +| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rt.AtIndex` of its element, which `add`, `sub`, `offset`, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view, and which a pointer to a value in no array fails at run time to move; a pointer into an array of values JS keeps as objects is refused; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | | `MaybeUninit` | `T`: `undefined` until written, or an object whose parts kept as objects are there, holding `undefined` | | `union` | an object of slots named by offset, `at0` and `at8`, each holding the field part kept there; fields must overlap in the same type, or in a non-null pointer and an `Option` of it | | an iterator | `rt.Iter`, whose adaptors are runtime functions: `rt.iterMap(rt.iterSlice(v), f)` | @@ -796,7 +796,7 @@ they need. ## Not yet -Byte reinterpretation, pointer arithmetic and addresses, raw allocation, and unions whose +Byte reinterpretation, pointer addresses, pointer arithmetic outside an array of values JS copies, raw allocation, and unions whose fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than `&dyn Debug`, `&dyn Display`, `dyn Future`, `dyn Any`, `dyn Fn*`, and the translated crates' traits' behind `&`, `&mut`, `Box`, or `Arc` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `dyn Error`, binds to @@ -865,7 +865,9 @@ built natively for wasm32 and run beside its translation on the same inputs by `NonZero`, and raw pointers: a linked list of boxed nodes as `hashlink` keeps one, through a union and a `MaybeUninit` entry, pointers to locals, fields, and whole structs, those `UnsafeCell::get` gives, as crossbeam-epoch's own `OnceLock` keeps its collector, and - those a generic type holds to its parameter, at each form a pointer takes, and a + those a generic type holds to its parameter, at each form a pointer takes, pointers + into a `Vec`, an array, a boxed slice, and a `MaybeUninit` array, as `smallvec` and + moq-net's frame segments move and copy through them, and a generic type's arrays, slices, `Vec`s, and `Cow`s of its parameter at bytes, at `u16`, and at values that drop. Its differential test runs through `caller.ts`, a diff --git a/ts/fixtures/crates/dep/src/lib.rs b/ts/fixtures/crates/dep/src/lib.rs index 2b5a0be32..d2caf2c0c 100644 --- a/ts/fixtures/crates/dep/src/lib.rs +++ b/ts/fixtures/crates/dep/src/lib.rs @@ -126,7 +126,7 @@ fn read(bytes: &[u8]) -> u8 { pub fn last(bytes: &[u8]) -> u8 { assert!(!bytes.is_empty(), "no last byte"); // SAFETY: it holds a byte. - unsafe { *bytes.as_ptr().add(bytes.len() - 1) } + unsafe { *bytes.as_ptr().byte_add(bytes.len() - 1) } } /// What [`tally`] counts with, through trait objects of either crate's types. diff --git a/ts/fixtures/crates/refused.txt b/ts/fixtures/crates/refused.txt index bfc265279..844f8718b 100644 --- a/ts/fixtures/crates/refused.txt +++ b/ts/fixtures/crates/refused.txt @@ -1,2 +1 @@ -unsupported call to `core::slice::::as_ptr` at fixtures/crates/dep/src/lib.rs:129:18 -unsupported call to `std::ptr::const_ptr::::add` at fixtures/crates/dep/src/lib.rs:129:27 +unsupported call to `std::ptr::const_ptr::::byte_add` at fixtures/crates/dep/src/lib.rs:129:27 diff --git a/ts/fixtures/lang/expected/core/bool.ts b/ts/fixtures/lang/expected/core/bool.ts index daa6d4fd5..28bd1226e 100644 --- a/ts/fixtures/lang/expected/core/bool.ts +++ b/ts/fixtures/lang/expected/core/bool.ts @@ -91,6 +91,13 @@ export function thenSome$1jqdi5p(self: boolean, t: rt.Duration): rt.Duration | u return t; } +export function thenSome$03chm1q(self: boolean, t: [rt.u8, rt.u8]): [rt.u8, rt.u8] | undefined { + if (!self) { + return undefined; + } + return t; +} + export function thenSome$0b23a33(self: boolean, t: rt.u16): rt.u16 | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/expected/core/option.ts b/ts/fixtures/lang/expected/core/option.ts index efd655972..3a2350068 100644 --- a/ts/fixtures/lang/expected/core/option.ts +++ b/ts/fixtures/lang/expected/core/option.ts @@ -636,6 +636,13 @@ export function map$12l9avg(self: rt.u8 | undefined, f: (a0: rt.u8) => Ticked.Ti return f(self); } +export function map$1fmzefe(self: rt.u8 | undefined, f: (a0: rt.u8) => [rt.u8, rt.u8]): [rt.u8, rt.u8] | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$1qp1ym6(self: rt.u8 | undefined, f: (a0: rt.u8) => rt.u32): rt.u32 | undefined { if (self === undefined) { return undefined; @@ -791,13 +798,6 @@ export function map$1pb5zw8(self: rt.u8 | undefined, f: (a0: rt.u8) => [rt.u16, return f(self); } -export function map$1fmzefe(self: rt.u8 | undefined, f: (a0: rt.u8) => [rt.u8, rt.u8]): [rt.u8, rt.u8] | undefined { - if (self === undefined) { - return undefined; - } - return f(self); -} - export function map$1c7zybz(self: rt.u8 | undefined, _: (a0: rt.u8) => rt.u32): rt.u32 | undefined { if (self === undefined) { return undefined; diff --git a/ts/fixtures/lang/expected/elements.Inline.ts b/ts/fixtures/lang/expected/elements.Inline.ts new file mode 100644 index 000000000..e47c3bd8b --- /dev/null +++ b/ts/fixtures/lang/expected/elements.Inline.ts @@ -0,0 +1,108 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { copy$Option_tuple2_u8_u8, copy$mem_MaybeUninit_array_Option_tuple2_u8_u8 } from "./lib.ts"; + +/** + * Up to four values kept inline in a `MaybeUninit` array, as `smallvec` keeps them, + * inserted and removed by moving the rest through pointers, and dropped in place. + */ +export class Inline { + len: rt.usize; + items: rt.ArrayOf; + + constructor(len: rt.usize, items: rt.ArrayOf) { + this.len = len; + this.items = items; + } +} + +export function new$Option_tuple2_u8_u8(): Inline<[rt.u8, rt.u8] | undefined> { + return new Inline(0, copy$mem_MaybeUninit_array_Option_tuple2_u8_u8((new Array(4) as unknown) as Array<[rt.u8, rt.u8] | undefined>)); +} + +export function new$sync_Arc_u8(): Inline> { + return new Inline(0, (new Array(4) as unknown) as rt.Arc[]); +} + +export function ptr$Option_tuple2_u8_u8(self: Inline<[rt.u8, rt.u8] | undefined>): rt.Place<[rt.u8, rt.u8] | undefined> { + return rt.sliceAsPtr(self.items as Array<[rt.u8, rt.u8] | undefined>); +} + +export function ptr$sync_Arc_u8(self: Inline>): rt.Place> { + return rt.sliceAsPtr(self.items as rt.Arc[]); +} + +export function asSlice$Option_tuple2_u8_u8(self: Inline<[rt.u8, rt.u8] | undefined>): Array<[rt.u8, rt.u8] | undefined> { + return rt.ptrSlice(rt.sliceAsPtr(self.items as Array<[rt.u8, rt.u8] | undefined>), self.len); +} + +export function asSlice$sync_Arc_u8(self: Inline>): rt.Arc[] { + return rt.ptrSlice(rt.sliceAsPtr(self.items as rt.Arc[]), self.len); +} + +export function insert$Option_tuple2_u8_u8(self: Inline<[rt.u8, rt.u8] | undefined>, at: rt.usize, value: [rt.u8, rt.u8] | undefined): void { + if (self.len < 4) { + if (at <= self.len) { + const len: rt.usize = self.len; + const p: rt.Place<[rt.u8, rt.u8] | undefined> = rt.ptrAdd(ptr$Option_tuple2_u8_u8(self), at); + rt.ptrCopy(p, rt.ptrAdd(p, 1), rt.usizeSub(len, at), copy$Option_tuple2_u8_u8); + p.v = copy$Option_tuple2_u8_u8(value); + self.len = rt.usizeAdd(self.len, 1); + return; + } + } + rt.panic("assertion failed: self.len < 4 && at <= self.len"); +} + +export function insert$sync_Arc_u8(self: Inline>, at: rt.usize, value: rt.Arc): void { + if (self.len < 4) { + if (at <= self.len) { + const len: rt.usize = self.len; + const p: rt.Place> = rt.ptrAdd(ptr$sync_Arc_u8(self), at); + rt.ptrCopy(p, rt.ptrAdd(p, 1), rt.usizeSub(len, at)); + p.v = value; + self.len = rt.usizeAdd(self.len, 1); + return; + } + } + rt.panic("assertion failed: self.len < 4 && at <= self.len"); +} + +export function remove$Option_tuple2_u8_u8(self: Inline<[rt.u8, rt.u8] | undefined>, at: rt.usize): [rt.u8, rt.u8] | undefined { + if (!(at < self.len)) { + rt.panic("assertion failed: at < self.len"); + } else { + self.len = rt.usizeSub(self.len, 1); + const len: rt.usize = self.len; + const p: rt.Place<[rt.u8, rt.u8] | undefined> = rt.ptrAdd(ptr$Option_tuple2_u8_u8(self), at); + const value: [rt.u8, rt.u8] | undefined = copy$Option_tuple2_u8_u8(p.v); + rt.ptrCopy(rt.ptrAdd(p, 1), p, rt.usizeSub(len, at), copy$Option_tuple2_u8_u8); + return value; + } +} + +export function remove$sync_Arc_u8(self: Inline>, at: rt.usize): rt.Arc { + if (!(at < self.len)) { + rt.panic("assertion failed: at < self.len"); + } else { + self.len = rt.usizeSub(self.len, 1); + const len: rt.usize = self.len; + const p: rt.Place> = rt.ptrAdd(ptr$sync_Arc_u8(self), at); + const value: rt.Arc = p.v; + rt.ptrCopy(rt.ptrAdd(p, 1), p, rt.usizeSub(len, at)); + return value; + } +} + +export function drop$ops_Drop_Option_tuple2_u8_u8(self: Inline<[rt.u8, rt.u8] | undefined>): void { + const len: rt.usize = self.len; + { v: rt.ptrSlice(ptr$Option_tuple2_u8_u8(self), len) }; +} + +export function drop$ops_Drop_sync_Arc_u8(self: Inline>): void { + const len: rt.usize = self.len; + rt.dropAll({ v: rt.ptrSlice(ptr$sync_Arc_u8(self), len) }.v); +} + +//# sourceMappingURL=elements.Inline.ts.map diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts new file mode 100644 index 000000000..6e364146e --- /dev/null +++ b/ts/fixtures/lang/expected/elements.ts @@ -0,0 +1,207 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { copy$Option_tuple2_u8_u8, copy$array_Option_tuple2_u8_u8, drop$elements_Inline_Option_tuple2_u8_u8, drop$elements_Inline_sync_Arc_u8 } from "./lib.ts"; +import { thenSome$03chm1q } from "./core/bool.ts"; +import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1fmzefe as option_map$1fmzefe, mapOr$1nlkf9b as option_mapOr$1nlkf9b } from "./core/option.ts"; +import * as Inline from "./elements.Inline.ts"; +import * as elements_Segment from "./elements.Segment.ts"; + +/** + * A `Vec`'s elements moved within it as `insert` and `remove` move them, and copied to + * another, read back through slices made of the pointers. + */ +export function vecs(data: rt.ByteSlice, out: rt.ByteVec): void { + const v: rt.u32[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (_2: rt.u8): rt.u32 => { + return rt.u32Mul(_2 as rt.u32, 3); + }), undefined, 4, true); + rt.iterExtend(v, rt.iterSlice([7, 8]), undefined, 4, true); + const len: rt.usize = v.length; + const at: rt.usize = rt.usizeRem(option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0) as rt.usize, rt.usizeSub(len, 1)); + const p: rt.Place = rt.sliceAsPtr(v); + rt.ptrCopy(rt.ptrAdd(p, at), rt.ptrAdd(p, rt.usizeAdd(at, 1)), rt.usizeSub(rt.usizeSub(len, at), 1)); + rt.ptrAdd(p, at).v = 1000; + emit(rt.ptrSlice(p, len), out); + rt.ptrCopy(rt.ptrAdd(p, rt.usizeAdd(at, 1)), rt.ptrAdd(p, at), rt.usizeSub(rt.usizeSub(len, at), 1)); + rt.ptrAdd(rt.ptrAdd(p, rt.usizeSub(len, 1)), -0).v = 2000; + emit(rt.ptrSlice(p, len), out); + const end: rt.Place = rt.ptrAdd(p, len); + const last: rt.Place = rt.ptrAdd(end, -1); + const _54: rt.Place = rt.ptrAdd(last, -1); + _54.v = rt.u32Add(_54.v, 1); + const _56: rt.Place = rt.ptrAdd(rt.ptrAdd(p, 1), -1); + _56.v = rt.u32Add(_56.v, 5); + rt.byteVecPush(out, rt.ptrOffsetFrom(end, p) & 0xff); + rt.byteVecPush(out, rt.ptrOffsetFrom(p, last) & 0xff); + rt.byteVecPush(out, rt.ptrOffsetFrom(last, last) & 0xff); + const part: rt.u32[] = rt.ptrSlice(rt.ptrAdd(p, at), rt.usizeSub(len, at)); + rt.idx(part, 0); + part[0] = rt.u32Add(part[0], 3); + part.reverse(); + const w: rt.u32[] = rt.vecFromElem(0, len, undefined); + rt.ptrCopy(p, rt.sliceAsPtr(w), len); + rt.ptrCopy(p, rt.ptrAdd(rt.sliceAsPtr(w), at), 1); + rt.ptrCopy(p, rt.ptrAdd(rt.sliceAsPtr(w), rt.usizeSub(len, 1)), 1); + emit(w, out); + emit(v, out); + rt.byteVecPush(out, rt.ptrAdd(rt.sliceAsPtr(v), rt.usizeDiv(len, 2)).v & 0xff); + rt.byteVecPush(out, rt.ptrSlice(rt.DANGLING, 0).length & 0xff); +} + +/** An array's elements, from a pointer to one, to the array, and to its first. */ +export function arrays(data: rt.ByteSlice, out: rt.ByteVec): void { + for (const n of rt.byteSliceView(data)) { + const a: rt.u16[] = [n as rt.u16, 1, 2, 3, 4]; + const i: rt.usize = rt.usizeRem(n as rt.usize, 4); + if (!(i < 5)) { + rt.panic("index out of bounds: the len is " + 5 + " but the index is " + i); + } + const at: rt.Place = new rt.AtIndex(a, i); + at.v = 99; + const whole: rt.Place = rt.sliceAsPtr(a); + const _18: rt.Place = rt.ptrAdd(whole, rt.usizeAdd(i, 1)); + _18.v = rt.u16Add(_18.v, 10); + const _22: rt.u16 = n as rt.u16; + const _23: rt.Place = rt.ptrAdd(whole, 4); + _23.v = rt.u16Add(_23.v, _22); + const first: rt.Place = rt.sliceAsPtr(a); + rt.byteVecPush(out, rt.ptrOffsetFrom(at, whole) & 0xff); + rt.byteVecPush(out, rt.ptrOffsetFrom(first, at) & 0xff); + const p: rt.Place = first; + const _37: rt.Place = rt.ptrAdd(p, 2); + _37.v = rt.u16Add(_37.v, 1); + const _40: rt.Place = rt.ptrAdd(rt.ptrAdd(p, 3), -3); + _40.v = rt.u16Add(_40.v, 2); + rt.byteVecPush(out, rt.ptrOffsetFrom(rt.ptrAdd(p, 4), p) & 0xff); + const tail: rt.Place = { v: rt.ptrSlice(rt.ptrAdd(p, i), rt.usizeSub(5, i)) }; + rt.byteVecPush(out, tail.v.length & 0xff); + rt.sliceAsPtr(tail.v).v = 7; + const unit: rt.Place = rt.DANGLING_UNIT; + n as rt.usize; + rt.byteVecPush(out, unit === unit ? 1 : 0); + rt.iterExtendBytes(out, rt.iterFlatMap(rt.iterSlice(a), (x: rt.u16): Uint8Array => { + return rt.u16ToBeBytes(x).reverse(); + }, (value: Uint8Array) => rt.iterSlice(value), false, 2), undefined, true); + } +} + +/** + * A boxed slice of bytes kept behind a pointer to its first, as moq-net's frame + * segment keeps its storage: written by copies at offsets, read by slices of it, and + * freed as the slice it was. + */ +export function segments(data: rt.ByteSlice, out: rt.ByteVec): void { + const segment: elements_Segment.Segment = elements_Segment.new(rt.usizeAdd(data.length, 4)); + elements_Segment.write(segment, 0, data); + elements_Segment.write(segment, data.length, rt.byteSliceFrom(Uint8Array.of(1, 2, 3, 4))); + rt.byteVecExtendFromSlice(out, elements_Segment.read(segment, rt.usizeAdd(data.length, 4))); + const p: rt.Place = segment.data; + rt.ptrCopy(p, rt.ptrAdd(p, 2), data.length); + rt.byteVecExtendFromSlice(out, elements_Segment.read(segment, rt.usizeAdd(data.length, 4))); + rt.ptrCopy(rt.ptrAdd(p, 3), rt.ptrAdd(p, 1), data.length); + const _36: rt.Place = rt.ptrAdd(p, rt.usizeAdd(data.length, 3)); + _36.v = _36.v ^ 255; + rt.byteVecExtendFromSlice(out, elements_Segment.read(segment, rt.usizeAdd(data.length, 4))); + const v: rt.ByteVec = rt.byteSliceToByteVec(data); + rt.byteVecPush(v, 9); + const a: Uint8Array = rt.bytes(4); + const p2: rt.Place = rt.byteVecAsPtr(v); + const _52: rt.Place = rt.ptrAdd(p2, rt.usizeSub(v.length, 1)); + _52.v = rt.u8Add(_52.v, 1); + rt.ptrCopy(p2, rt.byteSliceAsPtr(rt.byteSliceFrom(a)), rt.min(v.length, 4)); + const byte: rt.Place = rt.ptrAdd(rt.byteSliceAsPtr(rt.byteSliceFrom(a)), 1); + rt.ptrAdd(byte, 2).v = 85; + rt.byteVecExtendFromSlice(out, rt.ptrByteSlice(rt.ptrAdd(byte, -1), 4)); + const one: rt.Place = new rt.AtIndex(a, 2); + one.v = one.v ^ 15; + rt.byteVecPush(out, one.v); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteVecView(v)), undefined, true); + segment[Symbol.dispose](); +} + +/** + * Shared values and `Option`s of pairs moved through an inline array, counting the + * shared values' owners, and a pair changed in place after its copy moved. + */ +export function inline(data: rt.ByteSlice, out: rt.ByteVec): void { + let item!: rt.Arc; + const shared: rt.Arc = new rt.Arc(0); + const held: Inline.Inline> = Inline.new$sync_Arc_u8(); + const pairs: Inline.Inline<[rt.u8, rt.u8] | undefined> = Inline.new$Option_tuple2_u8_u8(); + const iter: rt.Iter<[rt.usize, rt.u8]> = rt.iterEnumerate(rt.iterSlice(rt.byteSliceView(data))); + while (true) { + const _10: [rt.usize, rt.u8] | rt.Done = iter.next(); + if (_10 === rt.DONE) { + break; + } + const i: rt.usize = _10[0]; + const n: rt.u8 = _10[1]; + if (held.len === 4) { + const gone: rt.Arc = Inline.remove$sync_Arc_u8(held, rt.usizeRem(n as rt.usize, 4)); + rt.byteVecPush(out, gone.value); + Inline.remove$Option_tuple2_u8_u8(pairs, rt.usizeRem(n as rt.usize, 4)); + gone[Symbol.dispose](); + } + const at: rt.usize = rt.usizeRem(n as rt.usize, rt.usizeAdd(held.len, 1)); + if (rt.u8Rem(n, 3) === 0) { + item = rt.arcClone(shared); + } else { + item = new rt.Arc(n); + } + Inline.insert$sync_Arc_u8(held, at, item); + const _46: [rt.u8, rt.u8] | undefined = thenSome$03chm1q(rt.u8Rem(n, 2) === 0, [n, i & 0xff] as [rt.u8, rt.u8]); + Inline.insert$Option_tuple2_u8_u8(pairs, at, _46); + const _52: [rt.u8, rt.u8] | undefined | rt.None = rt.sliceFirst(Inline.asSlice$Option_tuple2_u8_u8(pairs), rt.None); + if (_52 !== rt.None) { + if (_52 !== undefined) { + rt.byteVecPush(out, _52[0]); + } + } + const pointer = Inline.ptr$Option_tuple2_u8_u8(pairs); + const _60: rt.Place<[rt.u8, rt.u8] | undefined> | undefined = pointer === rt.NULL ? undefined : pointer; + if (_60 !== undefined) { + if (_60.v !== undefined) { + _60.v[1] = rt.u8WrappingAdd(_60.v[1], 1); + } + } + rt.byteVecPush(out, shared.strong & 0xff); + rt.iterExtendBytes(out, rt.iterMap(rt.iterSlice(Inline.asSlice$sync_Arc_u8(held)), (x: rt.Arc): rt.u8 => { + return x.value; + }), undefined, true); + rt.iterExtendBytes(out, rt.iterFlatMap(rt.iterSlice(Inline.asSlice$Option_tuple2_u8_u8(pairs)), (p: [rt.u8, rt.u8] | undefined): Uint8Array => { + return option_mapOr$1nlkf9b(p, Uint8Array.of(0, 0), (_2: [rt.u8, rt.u8]): Uint8Array => { + return Uint8Array.of(_2[0], _2[1]); + }); + }, (value: Uint8Array) => rt.iterSlice(value), false, 2), undefined, true); + } + drop$elements_Inline_sync_Arc_u8(held); + rt.byteVecPush(out, shared.strong & 0xff); + const src: Array<[rt.u8, rt.u8] | undefined> = copy$array_Option_tuple2_u8_u8([[1, 2] as [rt.u8, rt.u8], undefined, option_map$1fmzefe(rt.byteSliceFirst(data), (_22: rt.u8): [rt.u8, rt.u8] => { + return [_22, 4] as [rt.u8, rt.u8]; + })]).map(copy$Option_tuple2_u8_u8); + const dst: Array<[rt.u8, rt.u8] | undefined> = rt.vecFromElem(undefined as [rt.u8, rt.u8] | undefined, 3, copy$Option_tuple2_u8_u8); + rt.ptrCopy(rt.sliceAsPtr(src), rt.sliceAsPtr(dst), 3, copy$Option_tuple2_u8_u8); + const at2 = rt.idx(dst, 0); + if (dst[at2] !== undefined) { + dst[at2][0] = 9; + } + const at3 = rt.idx(src, 2); + if (src[at3] !== undefined) { + src[at3][1] = 8; + } + rt.iterExtendBytes(out, rt.iterFlatMap(rt.iterChain(rt.iterSlice(src), rt.iterSlice(dst)), (p2: [rt.u8, rt.u8] | undefined): Uint8Array => { + return option_mapOr$1nlkf9b(p2, Uint8Array.of(0, 0), (_25: [rt.u8, rt.u8]): Uint8Array => { + return Uint8Array.of(_25[0], _25[1]); + }); + }, (value: Uint8Array) => rt.iterSlice(value), false, 2), undefined, true); + drop$elements_Inline_Option_tuple2_u8_u8(pairs); + shared[Symbol.dispose](); +} + +export function emit(values: rt.u32[], out: rt.ByteVec): void { + rt.iterExtendBytes(out, rt.iterFlatMap(rt.iterSlice(values), (x: rt.u32): Uint8Array => { + return rt.u32ToBeBytes(x).reverse(); + }, (value: Uint8Array) => rt.iterSlice(value), false, 4), undefined, true); +} + +//# sourceMappingURL=elements.ts.map diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 53f7a2b71..e75edaf5f 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -23,6 +23,7 @@ import { defaults as defaults_defaults } from "./defaults.ts"; import { durations } from "./durations.ts"; import { dyns, shared as dyns_shared, anys, fns } from "./dyns.ts"; import { edits as edits_edits, bytes as edits_bytes } from "./edits.ts"; +import { vecs as elements_vecs, arrays as elements_arrays, segments as elements_segments, inline } from "./elements.ts"; import { eqs } from "./errors.ts"; import { fields as fields_fields } from "./fields.ts"; import { floats, mulAdds as floats_mulAdds, formats as floats_formats, parses as floats_parses } from "./floats.ts"; @@ -44,7 +45,7 @@ import { u8s as methods_u8s, u16s as methods_u16s, u32s as methods_u32s, u64s as import { nested as nested_nested } from "./nested.ts"; import { objects } from "./objects.ts"; import { orders, bytes as orders_bytes, tuples as orders_tuples, held as orders_held, btrees } from "./orders.ts"; -import { lists as pointers_lists, shared as pointers_shared, places as pointers_places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound as pointers_rebound, lapsed, writers, made as pointers_made, segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken as pointers_taken, bytes as pointers_bytes, loaded, reattached, partWritten, aimed } from "./pointers.ts"; +import { lists as pointers_lists, shared as pointers_shared, places as pointers_places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound as pointers_rebound, lapsed, writers, made as pointers_made, segments as pointers_segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken as pointers_taken, bytes as pointers_bytes, loaded, reattached, partWritten, aimed } from "./pointers.ts"; import { refs, toRefs } from "./refs.ts"; import { results as results_results } from "./results.ts"; import { returns } from "./returns.ts"; @@ -224,7 +225,7 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::slices", cows_slices as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::bytes", cows_bytes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u8s", wraps_u8s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u16s", wraps_u16s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u32s", wraps_u32s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u64s", wraps_u64s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u128s", wraps_u128s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::usizes", wraps_usizes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i8s", wraps_i8s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i16s", wraps_i16s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i32s", wraps_i32s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i64s", wraps_i64s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i128s", wraps_i128s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::isizes", wraps_isizes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::enums", (_2174: rt.u8, data74: rt.ByteSlice, out75: rt.ByteVec): undefined => { enums(data74, out75); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::segments", (_2175: rt.u8, data75: rt.ByteSlice, out76: rt.ByteVec): undefined => { - segments(data75, out76); + pointers_segments(data75, out76); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::uninit_runs", (_2176: rt.u8, data76: rt.ByteSlice, out77: rt.ByteVec): undefined => { uninitRuns(data76, out77); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::beside_written", (_2177: rt.u8, data77: rt.ByteSlice, out78: rt.ByteVec): undefined => { @@ -267,7 +268,15 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) capacity_deques(a0153, a1153.data, a1153.start, a1153.length, a2153); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::heaps", (a0154: rt.u8, a1154: rt.ByteSlice, a2154: rt.ByteVec): undefined => { capacity_heaps(a0154, a1154.data, a1154.start, a1154.length, a2154); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::vecs", (_2193: rt.u8, data93: rt.ByteSlice, out94: rt.ByteVec): undefined => { + elements_vecs(data93, out94); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::arrays", (_2194: rt.u8, data94: rt.ByteSlice, out95: rt.ByteVec): undefined => { + elements_arrays(data94, out95); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::segments", (_2195: rt.u8, data95: rt.ByteSlice, out96: rt.ByteVec): undefined => { + elements_segments(data95, out96); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::inline", (_2196: rt.u8, data96: rt.ByteSlice, out97: rt.ByteVec): undefined => { + inline(data96, out97); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } /** Run one case. */ diff --git a/ts/fixtures/lang/expected/lib.ts b/ts/fixtures/lang/expected/lib.ts index 996feba46..0785efdf0 100644 --- a/ts/fixtures/lang/expected/lib.ts +++ b/ts/fixtures/lang/expected/lib.ts @@ -14,6 +14,7 @@ import type * as counts_Noisy from "./counts.Noisy.ts"; import type * as RcReleasing from "./counts.RcReleasing.ts"; import type * as RcShared from "./counts.RcShared.ts"; import type * as durations_Window from "./durations.Window.ts"; +import * as Inline from "./elements.Inline.ts"; import type * as Couple from "./fields.Couple.ts"; import type * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; @@ -215,6 +216,10 @@ export function copy$array_Option_Option_array5_u8(v: Array): Array<[rt.u8, rt.u8] | undefined> { + return v.map(copy$Option_tuple2_u8_u8); +} + export function copy$array_Option_u8(v: Array): Array { return v.slice(); } @@ -389,6 +394,20 @@ export function copy$locals_Twin(v: locals_Twin.Twin): locals_Twin.Twin { return { _0: v._0, _1: v._1 }; } +export function copy$mem_MaybeUninit_Option_tuple2_u8_u8(v: [rt.u8, rt.u8] | undefined): [rt.u8, rt.u8] | undefined { + if (v === undefined) { + return v; + } + return v === undefined ? v : copy$tuple2_u8_u8(v); +} + +export function copy$mem_MaybeUninit_array_Option_tuple2_u8_u8(v: Array<[rt.u8, rt.u8] | undefined>): Array<[rt.u8, rt.u8] | undefined> { + if (v === undefined) { + return v; + } + return v.map(copy$mem_MaybeUninit_Option_tuple2_u8_u8); +} + export function copy$mem_MaybeUninit_array_array0_u8(v: Uint8Array[]): Uint8Array[] { if (v === undefined) { return v; @@ -986,6 +1005,14 @@ export function drop$cell_RefCell_Vec_sync_Arc_counts_ArcReleasing(value: rt.Ref rt.dropAll(value.value); } +export function drop$elements_Inline_Option_tuple2_u8_u8(_1: Inline.Inline<[rt.u8, rt.u8] | undefined>): void { + Inline.drop$ops_Drop_Option_tuple2_u8_u8(_1); +} + +export function drop$elements_Inline_sync_Arc_u8(_1: Inline.Inline>): void { + Inline.drop$ops_Drop_sync_Arc_u8(_1); +} + export function drop$generics_Run_generics_Ticked(_1: generics_Run.Run): void { if (_1.tag === "Listed") { rt.dropAll(_1._0 as Ticked.Ticked[]); diff --git a/ts/fixtures/lang/expected/tuples.ts b/ts/fixtures/lang/expected/tuples.ts index 54e14d022..376eebe74 100644 --- a/ts/fixtures/lang/expected/tuples.ts +++ b/ts/fixtures/lang/expected/tuples.ts @@ -2,7 +2,7 @@ import * as rt from "./rt.ts"; import { copy$tuple2_u8_u8, copy$tuples_Spot, drop$Result_tuple2_tuples_Logged_u8_u8, drop$tuple2_Vec_tuples_Logged_tuples_Tally, drop$tuple2_tuples_Logged_u8, drop$tuple2_u8_tuples_Logged } from "./lib.ts"; -import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1pb5zw8 as option_map$1pb5zw8, map$1fmzefe as option_map$1fmzefe } from "./core/option.ts"; +import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1fmzefe as option_map$1fmzefe, map$1pb5zw8 as option_map$1pb5zw8 } from "./core/option.ts"; import { set$0xuu1fw as local_set$0xuu1fw } from "./std/thread/local.ts"; import * as Last from "./tuples.Last.ts"; import * as tuples_Logged from "./tuples.Logged.ts"; diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs new file mode 100644 index 000000000..9deda0288 --- /dev/null +++ b/ts/fixtures/lang/src/elements.rs @@ -0,0 +1,256 @@ +//! Pointers into arrays: a `Vec`'s, a slice's, an array's, a boxed slice's, and a +//! `MaybeUninit` array's elements, moved by `add`, `sub`, `offset`, and their wrapping +//! forms, measured by `offset_from`, copied between by `ptr::copy` and +//! `copy_nonoverlapping`, overlapping either way, and made slices again, as `smallvec`'s +//! inline storage and moq-net's frame segments use them. + +use std::mem::MaybeUninit; +use std::ptr::{self, NonNull}; +use std::slice; +use std::sync::Arc; + +/// A `Vec`'s elements moved within it as `insert` and `remove` move them, and copied to +/// another, read back through slices made of the pointers. +pub fn vecs(data: &[u8], out: &mut Vec) { + let mut v: Vec = data.iter().map(|&b| u32::from(b) * 3).collect(); + v.extend([7, 8]); + let len = v.len(); + let at = usize::from(data.first().copied().unwrap_or(0)) % (len - 1); + let p = v.as_mut_ptr(); + unsafe { + // The tail moved right over the last element, and the gap filled. + ptr::copy(p.add(at), p.add(at + 1), len - at - 1); + *p.add(at) = 1000; + emit(slice::from_raw_parts(p, len), out); + // And moved back left. + ptr::copy(p.add(at + 1), p.add(at), len - at - 1); + *p.add(len - 1).sub(0) = 2000; + emit(slice::from_raw_parts(p, len), out); + // Pointers moved forward and back, and measured. + let end = p.add(len); + let last = end.offset(-1); + *last.wrapping_sub(1) += 1; + *p.wrapping_add(1).wrapping_offset(-1) += 5; + out.push(end.offset_from(p) as u8); + out.push(p.offset_from(last) as u8); + out.push(last.offset_from(last) as u8); + // A part of it, written through a mutable slice of the pointers. + let part = slice::from_raw_parts_mut(p.add(at), len - at); + part[0] += 3; + part.reverse(); + let mut w = vec![0u32; len]; + ptr::copy_nonoverlapping(p, w.as_mut_ptr(), len); + w.as_mut_ptr().add(at).copy_from(p, 1); + p.copy_to_nonoverlapping(w.as_mut_ptr().add(len - 1), 1); + emit(&w, out); + } + emit(&v, out); + let shared: &[u32] = &v; + let q = shared.as_ptr(); + out.push(unsafe { *q.add(len / 2) } as u8); + // A slice of none from a dangling pointer, as an empty collection's is. + let none = unsafe { slice::from_raw_parts(NonNull::::dangling().as_ptr(), 0) }; + out.push(none.len() as u8); +} + +/// An array's elements, from a pointer to one, to the array, and to its first. +pub fn arrays(data: &[u8], out: &mut Vec) { + for &n in data { + let mut a = [u16::from(n), 1, 2, 3, 4]; + let i = usize::from(n) % 4; + // Each pointer is read and written through before the next borrow of `a`. + let at = &raw mut a[i]; + unsafe { *at = 99 }; + let whole = (&raw mut a).cast::(); + unsafe { + *whole.add(i + 1) += 10; + *whole.add(4) += u16::from(n); + } + let first = a.as_mut_ptr(); + unsafe { + out.push(at.offset_from(whole) as u8); + out.push(first.offset_from(at) as u8); + let p = NonNull::new_unchecked(first); + *p.add(2).as_ptr() += 1; + *p.add(3).sub(3).as_ptr() += 2; + out.push(p.add(4).offset_from(p) as u8); + let tail = NonNull::slice_from_raw_parts(p.add(i), 5 - i); + out.push(tail.as_ref().len() as u8); + tail.as_ptr().cast::().write(7); + } + // A pointer to `()` stays where it is. + let unit = NonNull::<()>::dangling(); + let moved = unsafe { unit.add(usize::from(n)) }; + out.push(u8::from(moved == unit)); + out.extend(a.iter().flat_map(|x| x.to_le_bytes())); + } +} + +/// A boxed slice of bytes kept behind a pointer to its first, as moq-net's frame +/// segment keeps its storage: written by copies at offsets, read by slices of it, and +/// freed as the slice it was. +pub fn segments(data: &[u8], out: &mut Vec) { + let segment = Storage::new(data.len() + 4); + unsafe { + segment.write(0, data); + segment.write(data.len(), &[1, 2, 3, 4]); + out.extend_from_slice(segment.read(data.len() + 4)); + // Overlapping moves of its bytes, either way. + let p = segment.data; + ptr::copy(p, p.add(2), data.len()); + out.extend_from_slice(segment.read(data.len() + 4)); + ptr::copy(p.add(3), p.add(1), data.len()); + *p.add(data.len() + 3) ^= 0xff; + out.extend_from_slice(segment.read(data.len() + 4)); + } + // A `Vec`'s bytes and an array's, through pointers to them. + let mut v = data.to_vec(); + v.push(9); + let mut a = [0u8; 4]; + unsafe { + let p = v.as_mut_ptr(); + *p.add(v.len() - 1) += 1; + ptr::copy_nonoverlapping(p, (&raw mut a).cast::(), v.len().min(4)); + let byte = (&raw mut a).cast::().add(1); + *byte.add(2) = 0x55; + out.extend_from_slice(slice::from_raw_parts(byte.sub(1), 4)); + let one = &raw mut a[2]; + *one ^= 0x0f; + out.push(*one); + } + out.extend(v); +} + +struct Storage { + data: *mut u8, + capacity: usize, +} + +impl Storage { + fn new(capacity: usize) -> Self { + let boxed: Box<[u8]> = vec![0u8; capacity].into_boxed_slice(); + let capacity = boxed.len(); + let data = Box::into_raw(boxed) as *mut u8; + Self { data, capacity } + } + + unsafe fn write(&self, offset: usize, src: &[u8]) { + assert!(offset + src.len() <= self.capacity); + unsafe { ptr::copy_nonoverlapping(src.as_ptr(), self.data.add(offset), src.len()) }; + } + + unsafe fn read(&self, len: usize) -> &[u8] { + unsafe { slice::from_raw_parts(self.data, len) } + } +} + +impl Drop for Storage { + fn drop(&mut self) { + unsafe { + let slice = ptr::slice_from_raw_parts_mut(self.data, self.capacity); + drop(Box::from_raw(slice)); + } + } +} + +/// Up to four values kept inline in a `MaybeUninit` array, as `smallvec` keeps them, +/// inserted and removed by moving the rest through pointers, and dropped in place. +struct Inline { + len: usize, + items: MaybeUninit<[T; 4]>, +} + +impl Inline { + fn new() -> Self { + Inline { + len: 0, + items: MaybeUninit::uninit(), + } + } + + fn ptr(&mut self) -> *mut T { + self.items.as_mut_ptr() as *mut T + } + + fn as_slice(&self) -> &[T] { + unsafe { slice::from_raw_parts(self.items.as_ptr() as *const T, self.len) } + } + + fn insert(&mut self, at: usize, value: T) { + assert!(self.len < 4 && at <= self.len); + let len = self.len; + unsafe { + let p = self.ptr().add(at); + ptr::copy(p, p.add(1), len - at); + ptr::write(p, value); + } + self.len += 1; + } + + fn remove(&mut self, at: usize) -> T { + assert!(at < self.len); + self.len -= 1; + let len = self.len; + unsafe { + let p = self.ptr().add(at); + let value = ptr::read(p); + ptr::copy(p.add(1), p, len - at); + value + } + } +} + +impl Drop for Inline { + fn drop(&mut self) { + let len = self.len; + unsafe { ptr::drop_in_place(ptr::slice_from_raw_parts_mut(self.ptr(), len)) }; + } +} + +/// Shared values and `Option`s of pairs moved through an inline array, counting the +/// shared values' owners, and a pair changed in place after its copy moved. +pub fn inline(data: &[u8], out: &mut Vec) { + let shared = Arc::new(0u8); + let mut held: Inline> = Inline::new(); + let mut pairs = Inline::new(); + for (i, &n) in data.iter().enumerate() { + if held.len == 4 { + let gone = held.remove(usize::from(n) % 4); + out.push(*gone); + pairs.remove(usize::from(n) % 4); + } + let at = usize::from(n) % (held.len + 1); + let item = match n % 3 { + 0 => shared.clone(), + _ => Arc::new(n), + }; + held.insert(at, item); + pairs.insert(at, (n % 2 == 0).then_some((n, i as u8))); + if let Some(Some(pair)) = pairs.as_slice().first() { + out.push(pair.0); + } + if let Some(Some(pair)) = unsafe { pairs.ptr().as_mut() } { + pair.1 = pair.1.wrapping_add(1); + } + out.push(Arc::strong_count(&shared) as u8); + out.extend(held.as_slice().iter().map(|x| **x)); + out.extend(pairs.as_slice().iter().flat_map(|p| p.map_or([0, 0], |(a, b)| [a, b]))); + } + drop(held); + out.push(Arc::strong_count(&shared) as u8); + // Copies that change apart. + let mut src = vec![Some((1u8, 2u8)), None, data.first().map(|&n| (n, 4))]; + let mut dst = vec![None; 3]; + unsafe { ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), 3) }; + if let Some(pair) = &mut dst[0] { + pair.0 = 9; + } + if let Some(pair) = &mut src[2] { + pair.1 = 8; + } + out.extend(src.iter().chain(&dst).flat_map(|p| p.map_or([0, 0], |(a, b)| [a, b]))); +} + +fn emit(values: &[u32], out: &mut Vec) { + out.extend(values.iter().flat_map(|x| x.to_le_bytes())); +} diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index e1583de67..89d304870 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -3,10 +3,10 @@ use crate::{ args, arrays, ascii, atomics, borrows, bounds, boxes, capacity, captures, cells, chars, chunks, clones, - collections, combinators, counts, cows, cycles, defaults, durations, dyns, edits, errors, fields, floats, fmt, - folds, generics, hashes, hints, holds, ints, iters, joins, keys, lengths, lists, locals, locks, methods, nested, - objects, orders, pointers, refs, results, returns, saturates, separated, shapes, singles, slices, splits, statics, - strs, tuples, utf8, wrappers, wraps, zeros, + collections, combinators, counts, cows, cycles, defaults, durations, dyns, edits, elements, errors, fields, floats, + fmt, folds, generics, hashes, hints, holds, ints, iters, joins, keys, lengths, lists, locals, locks, methods, + nested, objects, orders, pointers, refs, results, returns, saturates, separated, shapes, singles, slices, splits, + statics, strs, tuples, utf8, wrappers, wraps, zeros, }; /// The native side's entry: [`run`], except that a case starting with 0xff prints a @@ -279,6 +279,10 @@ fn cases() -> Vec<(&'static str, Case)> { ("capacity::deques", capacity::deques), ("capacity::heaps", capacity::heaps), ("capacity::collects", capacity::collects), + ("elements::vecs", |_, data, out| elements::vecs(data, out)), + ("elements::arrays", |_, data, out| elements::arrays(data, out)), + ("elements::segments", |_, data, out| elements::segments(data, out)), + ("elements::inline", |_, data, out| elements::inline(data, out)), ] } diff --git a/ts/fixtures/lang/src/lib.rs b/ts/fixtures/lang/src/lib.rs index e6c00b0f3..102dcf24c 100644 --- a/ts/fixtures/lang/src/lib.rs +++ b/ts/fixtures/lang/src/lib.rs @@ -18,7 +18,8 @@ //! hashes of bytes, ranges of `Bound`s and of the crate's own `RangeBounds`, the equality //! of `Result`s and std's errors, inclusive ranges read through each adaptor, iterators' //! size hints, the capacity overflows collections reserving by them panic with, the -//! capacity of a `Vec`, `VecDeque`, and `BinaryHeap` as each grows and shrinks, tuples of +//! capacity of a `Vec`, `VecDeque`, and `BinaryHeap` as each grows and shrinks, pointers +//! into arrays moved, measured, and copied between, tuples of //! collections collected and extended by their items, zero-sized values, which MIR //! never assigns, with std's impls of them, and functions generic over an array's length. //! @@ -56,6 +57,7 @@ mod defaults; mod durations; mod dyns; mod edits; +mod elements; mod errors; mod fields; mod floats; diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index efcc94afb..ce4fb0c79 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,7 +1,7 @@ # Census of `reject-fixture` -- Refusals: 217 -- Function instances: 270 +- Refusals: 224 +- Function instances: 273 - Generic functions no instance reached, lowered over their type parameters: 13 ## By construct @@ -9,15 +9,17 @@ | Count | Construct | First | | ---: | --- | --- | | 45 | a raw pointer to `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:603:10 | -| 25 | unsupported call to `_` | fixtures/reject/src/lib.rs:160:8 | +| 21 | unsupported call to `_` | fixtures/reject/src/lib.rs:160:8 | | 19 | a raw pointer to `_` in a variable assigned again while it may point there | fixtures/reject/src/lib.rs:611:10 | | 15 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:1332:33 | | 15 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | fixtures/reject/src/lib.rs:1650:18 | | 5 | `_` with a mutable reference to a value JS copies, which its mapping would copy | fixtures/reject/src/lib.rs:1230:22 | | 5 | a raw pointer to `_` in an element, a payload, or a union field, which is not supported yet | fixtures/reject/src/lib.rs:674:10 | | 4 | a variant of `_`, which is not supported: either is the same JS string | fixtures/reject/src/lib.rs:331:2 | +| 3 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:1705:3 | | 3 | a copy of a closure that changes what it captured | fixtures/reject/src/lib.rs:224:14 | | 3 | an assignment through a reference to `_` | fixtures/reject/src/lib.rs:83:2 | +| 3 | an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:1708:56 | | 3 | replacing the value behind a reference to `_` | fixtures/reject/src/lib.rs:91:2 | | 3 | unsupported type `_` | fixtures/reject/src/lib.rs:7:14 | | 2 | `_` is not supported: its `None` and `Some` would both be `rt.None` | fixtures/reject/src/lib.rs:16:15 | @@ -43,6 +45,8 @@ | 1 | `_` is not supported: its field `_` is `_`, typed `_`, whose `Pending` would be `rt.OuterPending` as `_` | fixtures/reject/src/lib.rs:497:2 | | 1 | `_` of `_` by `_`, which is not supported yet | fixtures/reject/src/lib.rs:253:6 | | 1 | `_` of `_`, which is not supported yet | fixtures/reject/src/lib.rs:533:4 | +| 1 | `_` of pointers to `()`, which has no bytes | fixtures/reject/src/lib.rs:2167:13 | +| 1 | `_` of pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2160:36 | | 1 | `_`, which is not supported yet | fixtures/reject/src/lib.rs:261:9 | | 1 | `_`, which reads a capacity that depends on whether the other handles of its storage dropped, which is not supported | fixtures/reject/src/lib.rs:383:6 | | 1 | `clone_from` of `_`, whose items' `clone_from` is their own, which is not supported yet | fixtures/reject/src/lib.rs:2113:4 | @@ -51,7 +55,6 @@ | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | fixtures/reject/src/lib.rs:209:40 | | 1 | a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet | fixtures/reject/src/lib.rs:202:8 | | 1 | a cast from `_` to `_` | fixtures/reject/src/lib.rs:68:2 | -| 1 | a cast from `_` to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:1708:33 | | 1 | a coercion of `_` to `_`, a pointer to a slice | fixtures/reject/src/lib.rs:1709:28 | | 1 | a coercion to `_` | fixtures/reject/src/lib.rs:291:2 | | 1 | a constant of type `_`, a pointer into memory translation can't name | fixtures/reject/src/lib.rs:773:3 | @@ -60,11 +63,15 @@ | 1 | a mapped guard of `_`, which JS can't write in place | fixtures/reject/src/lib.rs:2147:10 | | 1 | a mutable reference into a `Result`'s payload, passed on or kept | fixtures/reject/src/lib.rs:362:12 | | 1 | a mutable static, which any code may write | fixtures/reject/src/lib.rs:61:11 | +| 1 | a pointer into `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2154:12 | +| 1 | a pointer into an array of `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2159:30 | | 1 | a pointer's address, which JS has no counterpart of | fixtures/reject/src/lib.rs:555:17 | | 1 | a printable type must implement `Display` or `Debug` | fixtures/reject/src/lib.rs:64:1 | | 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | fixtures/reject/src/lib.rs:572:10 | | 1 | a reference through which a raw pointer may be made into `_` in an element, a payload, or a union field, which is not supported yet | fixtures/reject/src/lib.rs:1359:11 | | 1 | a scalar constant of type `_` | fixtures/reject/src/lib.rs:8:2 | +| 1 | a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2158:11 | +| 1 | a slice of `()`, which has no bytes, from a pointer | fixtures/reject/src/lib.rs:2167:39 | | 1 | a trait object of `_`, which is not supported yet | fixtures/reject/src/lib.rs:476:26 | | 1 | an upcast from `_` to `_`, which is not supported yet | fixtures/reject/src/lib.rs:527:32 | | 1 | arithmetic on `_` | fixtures/reject/src/lib.rs:8:2 | @@ -88,23 +95,23 @@ The type or module each refused type or path belongs to. | ---: | --- | --- | | 39 | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:682:15 | | 24 | `(u32, u32)` | fixtures/reject/src/lib.rs:603:10 | -| 6 | `[u8; 4]` | fixtures/reject/src/lib.rs:476:26 | +| 9 | `[u8; 4]` | fixtures/reject/src/lib.rs:476:26 | +| 7 | `(u8, u8)` | fixtures/reject/src/lib.rs:1267:31 | +| 7 | `u32` | fixtures/reject/src/lib.rs:550:27 | | 6 | `f16` | fixtures/reject/src/lib.rs:7:14 | | 6 | `std::borrow::Cow` | fixtures/reject/src/lib.rs:331:2 | -| 6 | `u32` | fixtures/reject/src/lib.rs:550:27 | | 5 | `Gap` | fixtures/reject/src/lib.rs:903:10 | | 5 | `Option` | fixtures/reject/src/lib.rs:16:15 | -| 5 | `u8` | fixtures/reject/src/lib.rs:253:6 | | 4 | `Iterator` | fixtures/reject/src/lib.rs:37:4 | | 4 | `std::pin::Pin` | fixtures/reject/src/lib.rs:91:2 | | 4 | `std::str` | fixtures/reject/src/lib.rs:434:4 | +| 4 | `u8` | fixtures/reject/src/lib.rs:253:6 | | 4 | `{closure}` | fixtures/reject/src/lib.rs:319:13 | -| 3 | `*const [u8; 4]` | fixtures/reject/src/lib.rs:1708:33 | | 3 | `Mid` | fixtures/reject/src/lib.rs:1350:34 | | 3 | `Outer` | fixtures/reject/src/lib.rs:1350:34 | -| 3 | `std::ptr` | fixtures/reject/src/lib.rs:1705:3 | | 3 | `std::vec::Vec` | fixtures/reject/src/lib.rs:310:16 | | 2 | `(u16, u16)` | fixtures/reject/src/lib.rs:1171:31 | +| 2 | `*const [u8; 4]` | fixtures/reject/src/lib.rs:1709:28 | | 2 | `InArray` | fixtures/reject/src/lib.rs:1297:9 | | 2 | `InPayload` | fixtures/reject/src/lib.rs:1309:9 | | 2 | `InTuple` | fixtures/reject/src/lib.rs:1301:9 | @@ -118,6 +125,7 @@ The type or module each refused type or path belongs to. | 2 | `dyn Scaled` | fixtures/reject/src/lib.rs:459:23 | | 2 | `dyn std::any::Any` | fixtures/reject/src/lib.rs:527:32 | | 2 | `dyn std::iter::Iterator` | fixtures/reject/src/lib.rs:11:16 | +| 2 | `offset_from` | fixtures/reject/src/lib.rs:2160:36 | | 2 | `slice` | fixtures/reject/src/lib.rs:21:4 | | 2 | `std::any::TypeId` | fixtures/reject/src/lib.rs:533:2 | | 2 | `std::collections::BTreeSet` | fixtures/reject/src/lib.rs:235:75 | @@ -125,7 +133,6 @@ The type or module each refused type or path belongs to. | 2 | `std::mem::MaybeUninit` | fixtures/reject/src/lib.rs:917:19 | | 2 | `std::ops::Range` | fixtures/reject/src/lib.rs:160:8 | | 2 | `std::option::Option` | fixtures/reject/src/lib.rs:369:2 | -| 2 | `std::ptr::const_ptr` | fixtures/reject/src/lib.rs:1708:73 | | 2 | `std::std` | fixtures/reject/src/lib.rs:562:11 | | 2 | `std::sync::Arc` | fixtures/reject/src/lib.rs:291:2 | | 2 | `std::sync::MutexGuard` | fixtures/reject/src/lib.rs:83:2 | @@ -137,9 +144,7 @@ The type or module each refused type or path belongs to. | 1 | `(u16, ByRefReplace)` | fixtures/reject/src/lib.rs:1592:32 | | 1 | `(u16, ByRefTake)` | fixtures/reject/src/lib.rs:1600:32 | | 1 | `(u16, InTuple)` | fixtures/reject/src/lib.rs:1341:33 | -| 1 | `(u8, u8)` | fixtures/reject/src/lib.rs:1267:31 | | 1 | `*const [u8]` | fixtures/reject/src/lib.rs:1709:28 | -| 1 | `*const u8` | fixtures/reject/src/lib.rs:1708:33 | | 1 | `*mut [u8; 4]` | fixtures/reject/src/lib.rs:1707:41 | | 1 | `Aliased` | fixtures/reject/src/lib.rs:1115:29 | | 1 | `Assigned` | fixtures/reject/src/lib.rs:2007:20 | @@ -218,6 +223,7 @@ The type or module each refused type or path belongs to. | 1 | `[InArray; 2]` | fixtures/reject/src/lib.rs:1332:33 | | 1 | `[InWritten; 1]` | fixtures/reject/src/lib.rs:1393:35 | | 1 | `[std::cell::UnsafeCell; 2]` | fixtures/reject/src/lib.rs:2086:20 | +| 1 | `[u32; 2]` | fixtures/reject/src/lib.rs:2172:13 | | 1 | `dyn Keyed` | fixtures/reject/src/lib.rs:527:32 | | 1 | `poll` | fixtures/reject/src/lib.rs:497:2 | | 1 | `rt.Poll` | fixtures/reject/src/lib.rs:497:2 | @@ -234,7 +240,8 @@ The type or module each refused type or path belongs to. | 1 | `std::num` | fixtures/reject/src/lib.rs:1272:4 | | 1 | `std::ops::RangeFrom` | fixtures/reject/src/lib.rs:979:32 | | 1 | `std::ops::RangeTo` | fixtures/reject/src/lib.rs:979:11 | -| 1 | `std::ptr::mut_ptr` | fixtures/reject/src/lib.rs:1708:56 | +| 1 | `std::ptr::const_ptr` | fixtures/reject/src/lib.rs:1712:58 | +| 1 | `std::ptr` | fixtures/reject/src/lib.rs:1707:14 | | 1 | `std::slice::IterMut` | fixtures/reject/src/lib.rs:761:15 | | 1 | `std::std::Layout` | fixtures/reject/src/lib.rs:560:15 | | 1 | `std::sync::Weak` | fixtures/reject/src/lib.rs:450:40 | @@ -256,6 +263,7 @@ The type or module each refused type or path belongs to. | 2 | `_` of `_`, whose `None` is `rt.None`, which is not supported yet | `Iterator::scan`, `Option` | fixtures/reject/src/lib.rs:37:4 | | 2 | `_`, a trait object of another crate's trait, which is not supported yet | `dyn std::iter::Iterator` | fixtures/reject/src/lib.rs:11:16 | | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | `dyn Scaled`, `Scaled` | fixtures/reject/src/lib.rs:459:23 | +| 2 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | `[u8; 4]` | fixtures/reject/src/lib.rs:1705:3 | | 2 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::mem::MaybeUninit`, `Gap` | fixtures/reject/src/lib.rs:917:19 | | 2 | a raw pointer to `_` in a variable assigned again while it may point there | `[u8; 4]` | fixtures/reject/src/lib.rs:1728:26 | | 2 | a raw pointer to `_` in a variable assigned again while it may point there | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:691:15 | @@ -264,6 +272,7 @@ The type or module each refused type or path belongs to. | 2 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `Inside` | fixtures/reject/src/lib.rs:1840:20 | | 2 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `[u8; 4]` | fixtures/reject/src/lib.rs:1728:26 | | 2 | an assignment through a reference to `_` | `std::pin::Pin` | fixtures/reject/src/lib.rs:107:2 | +| 2 | an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in | `[u8; 4]` | fixtures/reject/src/lib.rs:1708:56 | | 2 | comparing pointers to `_`, each a place JS makes of its own | `u32` | fixtures/reject/src/lib.rs:591:2 | | 2 | replacing the value behind a reference to `_` | `std::pin::Pin` | fixtures/reject/src/lib.rs:91:2 | | 2 | unsupported call to `_` | `::sum` | fixtures/reject/src/lib.rs:429:17 | @@ -278,6 +287,8 @@ The type or module each refused type or path belongs to. | 1 | `_` of `_`, which is not supported yet | `std::any::Any::type_id`, `dyn std::any::Any` | fixtures/reject/src/lib.rs:533:4 | | 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `slice::clone_from_slice`, `Reused` | fixtures/reject/src/lib.rs:286:4 | | 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `slice::fill`, `Reused` | fixtures/reject/src/lib.rs:21:4 | +| 1 | `_` of pointers to `()`, which has no bytes | `offset_from` | fixtures/reject/src/lib.rs:2167:13 | +| 1 | `_` of pointers to `_`, which JS keeps as an object that knows no array it is in | `offset_from`, `(u8, u8)` | fixtures/reject/src/lib.rs:2160:36 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::collections::LinkedList::retain` | fixtures/reject/src/lib.rs:2112:4 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::default::Default::default` | fixtures/reject/src/lib.rs:2114:2 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::iter::once` | fixtures/reject/src/lib.rs:1230:22 | @@ -291,17 +302,19 @@ The type or module each refused type or path belongs to. | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | `std::iter::Map` | fixtures/reject/src/lib.rs:209:40 | | 1 | a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet | `std::iter::Zip` | fixtures/reject/src/lib.rs:202:8 | | 1 | a cast from `_` to `_` | `u64`, `f16` | fixtures/reject/src/lib.rs:68:2 | -| 1 | a cast from `_` to `_`, which reads the bytes of one type as another | `*const [u8; 4]`, `*const u8` | fixtures/reject/src/lib.rs:1708:33 | -| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `[u8; 4]`, `u8` | fixtures/reject/src/lib.rs:1708:17 | +| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `[u32; 2]` | fixtures/reject/src/lib.rs:2172:13 | | 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `u8` | fixtures/reject/src/lib.rs:550:27 | | 1 | a coercion of `_` to `_`, a pointer to a slice | `*const [u8; 4]`, `*const [u8]` | fixtures/reject/src/lib.rs:1709:28 | | 1 | a coercion to `_` | `std::sync::Arc` | fixtures/reject/src/lib.rs:291:2 | | 1 | a coercion to `_`, which is not supported yet | `&Tail` | fixtures/reject/src/lib.rs:486:30 | | 1 | a coercion to `_`, which is not supported yet | `std::sync::Weak` | fixtures/reject/src/lib.rs:450:40 | | 1 | a constant of type `_`, a pointer into memory translation can't name | `&u32` | fixtures/reject/src/lib.rs:773:3 | +| 1 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2157:3 | | 1 | a dangling pointer to `_`, a value of no bytes made of more than structs and tuples | `Only` | fixtures/reject/src/lib.rs:732:11 | | 1 | a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is | `std::task::Poll` | fixtures/reject/src/lib.rs:77:34 | | 1 | a mapped guard of `_`, which JS can't write in place | `{closure}` | fixtures/reject/src/lib.rs:2147:10 | +| 1 | a pointer into `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2154:12 | +| 1 | a pointer into an array of `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2159:30 | | 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | `std::vec::Vec` | fixtures/reject/src/lib.rs:572:10 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u16, u16)`, `Copied` | fixtures/reject/src/lib.rs:1171:31 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u16, u16)`, `Pushed` | fixtures/reject/src/lib.rs:1243:22 | @@ -370,10 +383,12 @@ The type or module each refused type or path belongs to. | 1 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:1650:18 | | 1 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `std::option::Option` | fixtures/reject/src/lib.rs:2103:13 | | 1 | a scalar constant of type `_` | `f16` | fixtures/reject/src/lib.rs:8:2 | +| 1 | a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2158:11 | | 1 | a trait object of `_`, which is not supported yet | `[u8; 4]` | fixtures/reject/src/lib.rs:476:26 | | 1 | an assignment through a reference to `_` | `std::sync::MutexGuard` | fixtures/reject/src/lib.rs:83:2 | | 1 | an iterator of the crate implementing `_`, which is not supported yet | `DoubleEndedIterator` | fixtures/reject/src/lib.rs:145:1 | | 1 | an iterator of the crate implementing `_`, which is not supported yet | `FusedIterator` | fixtures/reject/src/lib.rs:193:1 | +| 1 | an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2156:13 | | 1 | an upcast from `_` to `_`, which is not supported yet | `dyn Keyed`, `dyn std::any::Any` | fixtures/reject/src/lib.rs:527:32 | | 1 | arithmetic on `_` | `f16` | fixtures/reject/src/lib.rs:8:2 | | 1 | comparing `_` | `*mut [u8; 4]` | fixtures/reject/src/lib.rs:1707:41 | @@ -406,10 +421,6 @@ The type or module each refused type or path belongs to. | 1 | unsupported call to `_` | `std::ops::RangeTo::contains` | fixtures/reject/src/lib.rs:979:11 | | 1 | unsupported call to `_` | `std::ptr::addr_eq` | fixtures/reject/src/lib.rs:1707:14 | | 1 | unsupported call to `_` | `std::ptr::const_ptr::len` | fixtures/reject/src/lib.rs:1712:58 | -| 1 | unsupported call to `_` | `std::ptr::const_ptr::offset` | fixtures/reject/src/lib.rs:1708:73 | -| 1 | unsupported call to `_` | `std::ptr::copy_nonoverlapping` | fixtures/reject/src/lib.rs:1706:3 | -| 1 | unsupported call to `_` | `std::ptr::copy` | fixtures/reject/src/lib.rs:1705:3 | -| 1 | unsupported call to `_` | `std::ptr::mut_ptr::add` | fixtures/reject/src/lib.rs:1708:56 | | 1 | unsupported call to `_` | `std::std::Layout::new` | fixtures/reject/src/lib.rs:560:15 | | 1 | unsupported call to `_` | `std::std::alloc` | fixtures/reject/src/lib.rs:562:11 | | 1 | unsupported call to `_` | `std::std::dealloc` | fixtures/reject/src/lib.rs:565:3 | @@ -470,7 +481,7 @@ The type or module each refused type or path belongs to. -
unsupported call to `_` (25) +
unsupported call to `_` (21) - fixtures/reject/src/lib.rs:160:8: unsupported call to `std::ops::Range::::contains::` - fixtures/reject/src/lib.rs:301:10: unsupported call to `::hash::` @@ -491,11 +502,7 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:984:9: unsupported call to `std::ops::Range::<&mut u8>::is_empty` - fixtures/reject/src/lib.rs:1272:4: unsupported call to `core::num::::is_ascii_octdigit` - fixtures/reject/src/lib.rs:1631:13: unsupported call to `core::str::::as_bytes_mut` -- fixtures/reject/src/lib.rs:1705:3: unsupported call to `std::ptr::copy::<[u8; 4]>` -- fixtures/reject/src/lib.rs:1706:3: unsupported call to `std::ptr::copy_nonoverlapping::<[u8; 4]>` - fixtures/reject/src/lib.rs:1707:14: unsupported call to `std::ptr::addr_eq::<[u8; 4], [u8; 4]>` -- fixtures/reject/src/lib.rs:1708:56: unsupported call to `std::ptr::mut_ptr::::add` -- fixtures/reject/src/lib.rs:1708:73: unsupported call to `std::ptr::const_ptr::::offset` - fixtures/reject/src/lib.rs:1712:58: unsupported call to `std::ptr::const_ptr::::len`
@@ -593,6 +600,14 @@ The type or module each refused type or path belongs to.
+
a copy between pointers to `_`, which JS keeps as an object that knows no array it is in (3) + +- fixtures/reject/src/lib.rs:1705:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:1706:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:2157:3: a copy between pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+
a copy of a closure that changes what it captured (3) - fixtures/reject/src/lib.rs:224:14: a copy of a closure that changes what it captured @@ -609,6 +624,14 @@ The type or module each refused type or path belongs to.
+
an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in (3) + +- fixtures/reject/src/lib.rs:1708:56: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:1708:73: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:2156:13: an offset of a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+
replacing the value behind a reference to `_` (3) - fixtures/reject/src/lib.rs:91:2: replacing the value behind a reference to `std::pin::Pin fn(&'a mut std::task::Context<'b>) -> std::task::Poll>>>` @@ -663,7 +686,7 @@ The type or module each refused type or path belongs to.
a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another (2) - fixtures/reject/src/lib.rs:550:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another -- fixtures/reject/src/lib.rs:1708:17: a cast from a pointer to `[u8; 4]` to one to `u8`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2172:13: a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another
@@ -776,6 +799,18 @@ The type or module each refused type or path belongs to.
+
`_` of pointers to `()`, which has no bytes (1) + +- fixtures/reject/src/lib.rs:2167:13: `offset_from` of pointers to `()`, which has no bytes + +
+ +
`_` of pointers to `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2160:36: `offset_from` of pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+
`_`, which is not supported yet (1) - fixtures/reject/src/lib.rs:261:9: `Hasher::write`, which is not supported yet @@ -824,12 +859,6 @@ The type or module each refused type or path belongs to.
-
a cast from `_` to `_`, which reads the bytes of one type as another (1) - -- fixtures/reject/src/lib.rs:1708:33: a cast from `*const [u8; 4]` to `*const u8`, which reads the bytes of one type as another - -
-
a coercion of `_` to `_`, a pointer to a slice (1) - fixtures/reject/src/lib.rs:1709:28: a coercion of `*const [u8; 4]` to `*const [u8]`, a pointer to a slice @@ -878,6 +907,18 @@ The type or module each refused type or path belongs to.
+
a pointer into `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2154:12: a pointer into `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+ +
a pointer into an array of `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2159:30: a pointer into an array of `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+
a pointer's address, which JS has no counterpart of (1) - fixtures/reject/src/lib.rs:555:17: a pointer's address, which JS has no counterpart of @@ -908,6 +949,18 @@ The type or module each refused type or path belongs to.
+
a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2158:11: a slice from a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+ +
a slice of `()`, which has no bytes, from a pointer (1) + +- fixtures/reject/src/lib.rs:2167:39: a slice of `()`, which has no bytes, from a pointer + +
+
a trait object of `_`, which is not supported yet (1) - fixtures/reject/src/lib.rs:476:26: a trait object of `[u8; 4]`, which is not supported yet diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index fa889754b..8f482cc3c 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -175,14 +175,12 @@ a raw pointer to `ByKeyTake` in `(u16, ByKeyTake)`, which the crate may put anot unsupported call to `core::str::::as_bytes_mut` at fixtures/reject/src/lib.rs:1631:13 `core::str::::get_unchecked_mut` with a mutable reference to a value JS copies, which its mapping would copy at fixtures/reject/src/lib.rs:1632:13 `std::str::from_utf8_mut` with a mutable reference to a value JS copies, which its mapping would copy at fixtures/reject/src/lib.rs:1637:20 -unsupported call to `std::ptr::copy::<[u8; 4]>` at fixtures/reject/src/lib.rs:1705:3 -unsupported call to `std::ptr::copy_nonoverlapping::<[u8; 4]>` at fixtures/reject/src/lib.rs:1706:3 +a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:1705:3 +a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:1706:3 unsupported call to `std::ptr::addr_eq::<[u8; 4], [u8; 4]>` at fixtures/reject/src/lib.rs:1707:14 comparing `*mut [u8; 4]` at fixtures/reject/src/lib.rs:1707:41 -a cast from a pointer to `[u8; 4]` to one to `u8`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:1708:17 -a cast from `*const [u8; 4]` to `*const u8`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:1708:33 -unsupported call to `std::ptr::mut_ptr::::add` at fixtures/reject/src/lib.rs:1708:56 -unsupported call to `std::ptr::const_ptr::::offset` at fixtures/reject/src/lib.rs:1708:73 +an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:1708:56 +an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:1708:73 a coercion of `*const [u8; 4]` to `*const [u8]`, a pointer to a slice at fixtures/reject/src/lib.rs:1709:28 hashing `*const [u8; 4]` by its address, which JS has no counterpart of at fixtures/reject/src/lib.rs:1711:5 unsupported call to `std::ptr::const_ptr::::len` at fixtures/reject/src/lib.rs:1712:58 @@ -198,6 +196,15 @@ const generic types are not supported: `Block` at fixtures/reject/src/lib.rs:209 `clone_from` of `std::collections::LinkedList`, whose items' `clone_from` is their own, which is not supported yet at fixtures/reject/src/lib.rs:2113:4 `std::default::Default::default` with a mutable reference to a value JS copies, which its mapping would copy at fixtures/reject/src/lib.rs:2114:2 a mapped guard of `{closure@fixtures/reject/src/lib.rs:2146:38: 2146:50}`, which JS can't write in place at fixtures/reject/src/lib.rs:2147:10 +a pointer into `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2154:12 +an offset of a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2156:13 +a copy between pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2157:3 +a slice from a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2158:11 +a pointer into an array of `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2159:30 +`offset_from` of pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2160:36 +`offset_from` of pointers to `()`, which has no bytes at fixtures/reject/src/lib.rs:2167:13 +a slice of `()`, which has no bytes, from a pointer at fixtures/reject/src/lib.rs:2167:39 +a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2172:13 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:125:2 an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet at fixtures/reject/src/lib.rs:145:1 overriding `Iterator::size_hint`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:173:2 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index ceb01a3dc..f282f4216 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -1695,8 +1695,8 @@ pub fn swapped_in_loop(n: u32) -> u32 { } /// Pointer operations on arrays of bytes that read the address or other bytes than the -/// array's: comparing addresses only, copying, offsetting, casting to another pointee, -/// hashing, and ordering. +/// array's: comparing addresses only, copying, offsetting, hashing, and ordering, beside +/// pointers to their first bytes. pub fn byte_pointer_ops(mut a: [u8; 4], b: [u8; 4]) -> u64 { use std::hash::{Hash, Hasher}; let p: *mut [u8; 4] = &mut a; @@ -2147,3 +2147,27 @@ pub fn mapped_closure(add: u8) -> u8 { let f = std::cell::RefMut::map(cell.borrow_mut(), |pair| &mut pair.0); f(1) } + +/// Pointers into arrays whose elements JS keeps as objects: a pointer to one is the +/// object, which knows no array it is in. +pub fn object_elements(v: &mut Vec<(u8, u8)>, a: [(u8, u8); 2]) -> u8 { + let p = v.as_mut_ptr(); + unsafe { + let q = p.add(1); + std::ptr::copy(p, q, 1); + let s = std::slice::from_raw_parts(p, 2); + let first = (&raw const a).cast::<(u8, u8)>(); + (*q).0 + s[1].1 + (*first).0 + q.offset_from(p) as u8 + } +} + +/// Pointers to values of no bytes, which std's `offset_from` panics on, and a slice of +/// them from one, which has no array to be a view of. +pub fn unit_elements(p: *const ()) -> usize { + unsafe { p.offset_from(p) as usize + std::slice::from_raw_parts(p, 3).len() } +} + +/// A pointer to an element cast to one to an array of elements from it. +pub fn element_to_array(v: &[u32]) -> u32 { + unsafe { (*(v.as_ptr() as *const [u32; 2]))[1] } +} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 3cee45be3..9d2b70c1d 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -165,5 +165,8 @@ roots = [ "block_cloned", "lists", "mapped_closure", + "object_elements", + "unit_elements", + "element_to_array", ] printable = ["Meters"] diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 605b72ce4..20161959d 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -143,6 +143,78 @@ export type VecOf = T[] | ByteVec; /** A generic type's `Cow<[T]>`: the `Cow`, or the `ByteCow` of a `Cow<[u8]>`. */ export type CowOf = Cow | ByteCow; +/** The pointer to element `index` of `s`, a slice, a `[T; N]`, or a `Vec`'s storage. */ +export function sliceAsPtr(s: T[], index = 0): Place { + const view = (s as { [VIEW]?: View })[VIEW]; + return view ? new AtIndex(view.s, view.start + index) : new AtIndex(s, index); +} + +/** The pointer to byte `index` of `s`. */ +export function byteSliceAsPtr(s: ByteSlice, index = 0): Place { + return new AtIndex(s.data, s.start + index); +} + +/** The pointer to the first byte of `v`'s storage. */ +export function byteVecAsPtr(v: ByteVec): Place { + return new AtIndex(v.data, 0); +} + +/** The array and index `p` points at, which only a pointer into an array has. */ +function elemOf(p: Place, what: string): AtIndex { + if (p instanceof AtIndex) return p; + throw new Error(`rust2-ts: ${what} of a pointer to a value in no array`); +} + +/** `p.add(n)`, `p.offset(n)`, and their wrapping forms: `n` elements past where `p` points. */ +export function ptrAdd(p: Place, n: number): Place { + if (n === 0) return p; + const at = elemOf(p, "an offset"); + return new AtIndex(at.array, at.index + n); +} + +/** `p.offset_from(origin)`: how many elements `p` is past `origin`, in the same array. */ +export function ptrOffsetFrom(p: Place, origin: Place): number { + if (p === origin) return 0; + const [a, b] = [elemOf(p, "`offset_from`"), elemOf(origin, "`offset_from`")]; + if (a.array !== b.array) throw new Error("rust2-ts: `offset_from` of pointers into different arrays"); + return a.index - b.index; +} + +/** + * `ptr::copy` and `copy_nonoverlapping` of `n` elements from `src` to `dst`, as if through + * a buffer between, each made a value of its own by `copy` where the crate writes into its parts. + */ +export function ptrCopy(src: Place, dst: Place, n: number, copy?: (value: T) => T): void { + if (n === 0) return; + if (n === 1 && !(src instanceof AtIndex && dst instanceof AtIndex)) { + dst.v = copy ? copy(src.v) : src.v; + return; + } + const [from, to] = [elemOf(src, "a copy"), elemOf(dst, "a copy")]; + if (ArrayBuffer.isView(from.array) && ArrayBuffer.isView(to.array)) { + const bytes = from.array as unknown as Uint8Array; + (to.array as unknown as Uint8Array).set(bytes.subarray(from.index, from.index + n), to.index); + return; + } + const values = Array.from({ length: n }, (_, i) => from.array[from.index + i]); + for (let i = 0; i < n; i++) new AtIndex(to.array, to.index + i).v = copy ? copy(values[i]) : values[i]; +} + +/** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` elements from `p`. */ +export function ptrSlice(p: Place, len: number): T[] { + if (len === 0) return []; + const at = elemOf(p, "a slice"); + const array = at.array as T[]; + return at.index === 0 && len === array.length ? array : sliceView(array, at.index, at.index + len); +} + +/** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` bytes from `p`. */ +export function ptrByteSlice(p: Place, len: number): ByteSlice { + if (len === 0) return new ByteSlice(new Uint8Array(0), 0, 0); + const at = elemOf(p, "a slice"); + return new ByteSlice(at.array as Uint8Array, at.index, len); +} + /** * `ptr::write` of `value` through a pointer to an object: `value` copied into it, one * object deep, so every pointer to it sees the write. diff --git a/ts/runtime/rt/collections.ts b/ts/runtime/rt/collections.ts index 47057b12b..5189256d8 100644 --- a/ts/runtime/rt/collections.ts +++ b/ts/runtime/rt/collections.ts @@ -361,22 +361,26 @@ function itself(value: T): T { return value; } -/** The place of index `i` of `a`. */ +/** The place of element `index` of `array`, which a raw pointer into it is too. */ export class AtIndex implements Place { - private readonly a: { [i: number]: T }; - private readonly i: number; + readonly array: { [i: number]: T }; + readonly index: number; - constructor(a: { [i: number]: T }, i: number) { - this.a = a; - this.i = i; + constructor(array: { [i: number]: T }, index: number) { + this.array = array; + this.index = index; } get v(): T { - return this.a[this.i]; + return this.array[this.index]; } set v(value: T) { - this.a[this.i] = value; + // A JS array grows by a write past its end, where a `Vec`'s length stays until `set_len`. + if (Array.isArray(this.array) && this.index >= this.array.length) { + throw new Error("rust2-ts: a write through a pointer past the end of an array"); + } + this.array[this.index] = value; } } diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index a7bd83a66..846a4831a 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -128,12 +128,17 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// copies its bytes: the JS objects it holds by value copied where the crate's code /// writes into them in place. pub fn snapshot(&mut self, value: Expr, ty: Ty<'tcx>, span: Span) -> Expr { - match self.changes(ty).then(|| self.shim(ty, span)).flatten() { + match self.snapshot_fn(ty, span) { Some(copy) => copy.call(vec![value]), None => value, } } + /// The function [`Self::snapshot`] calls on a `ty`, if it copies one. + pub fn snapshot_fn(&mut self, ty: Ty<'tcx>, span: Span) -> Option { + self.changes(ty).then(|| self.shim(ty, span)).flatten() + } + /// `value`, copied if a `ty` is. pub fn copied(&mut self, value: Expr, ty: Ty<'tcx>, span: Span) -> Expr { match self.copy_fn(ty, span) { diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 771f1a143..c968ce443 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -114,6 +114,22 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { "a raw pointer to `{pointee}` behind a reference, which is the object and not where it is kept" ), ), + // A pointer to an element is where it is in its array, which pointer + // arithmetic moves within. + [ + .., + mir::ProjectionElem::Index(_) | mir::ProjectionElem::ConstantIndex { .. }, + ] => { + let target = self.place_expr(*place, span, out); + let target = self.stable_target(*place, target, span, out); + match target.unlocated() { + Expr::Index(array, index) => Expr::New( + Box::new(Expr::Rt("AtIndex".into())), + vec![*array.clone(), *index.clone()], + ), + _ => self.place_of(target, None), + } + } _ => { let target = self.place_expr(*place, span, out); self.make_place(*place, target, span, out) @@ -325,13 +341,14 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { | PointerCoercion::MutToConstPointer => self.operand(op, span, out), _ => self.err(span, format!("unsupported pointer coercion {coercion:?}")), }, - // The same pointer, as one to the same pointee: `*mut T` as `*const T`, or a - // `MaybeUninit` as the `T` it holds. + // The same pointer, as one to the same pointee, `*mut T` as `*const T` or a + // `MaybeUninit` as the `T` it holds, or one to the first element of an array. CastKind::PtrToPtr if let (Some(a), Some(b)) = (from.builtin_deref(true), to.builtin_deref(true)) - && self.b.t.uninit_inner(a) == self.b.t.uninit_inner(b) => + && from.is_raw_ptr() => { - self.operand(op, span, out) + let p = self.operand(op, span, out); + crate::lower::pointer::cast(self.b, p, a, b, span) } CastKind::PtrToPtr => self.err( span, diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 4020436c3..0e5bbe8fc 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -7,6 +7,10 @@ //! value JS copies is, which a `Vec` or an `Arc` also is here: its JS object is what it //! owns, not where Rust keeps it. Null is `rt.NULL`, and a dangling pointer `rt.DANGLING`. //! +//! An `rt.Place` into an array, slice, or `Vec` is the `rt.AtIndex` of its element, which +//! pointer arithmetic moves within the array. A pointer to an element JS keeps as an +//! object is that object, which knows no array it is in, so arithmetic on one is refused. +//! //! A pointer to `()`, as std erases one, is an identity that compares as itself: null, //! `rt.DANGLING_UNIT`, or a waker's data. One to a place of `()` or from a reference to //! one is refused, as an `rt.Place` of it would be an identity of its own each borrow. @@ -460,19 +464,9 @@ pub fn call<'tcx>( } } ("NonNull", "dangling") | ("ptr", "dangling" | "dangling_mut") => Expr::Rt("DANGLING".into()), - // The same pointer, as one to what a `MaybeUninit` holds or the other way. ("ptr" | "NonNull", "cast") => { - let to = targ(gargs, 1); - match b.t.uninit_inner(pointee()) == b.t.uninit_inner(to) { - true => arg(), - false => b.err( - span, - format!( - "a cast from a pointer to `{}` to one to `{to}`, which reads the bytes of one type as another", - pointee() - ), - ), - } + let p = arg(); + cast(b, p, pointee(), targ(gargs, 1), span) } ("ptr" | "NonNull", "addr" | "expose_provenance" | "with_addr" | "map_addr" | "without_provenance") => { b.err(span, "a pointer's address, which JS has no counterpart of") @@ -627,12 +621,149 @@ pub fn call<'tcx>( ("UnsafeCell", "get") if bytes(b, pointee()) => to_pointer(arg().member("v")), ("UnsafeCell", "get_mut") if b.t.needs_place(pointee()) => arg(), ("UnsafeCell", "get" | "get_mut" | "into_inner") => arg().member("v"), + // Pointers into an array: `rt.AtIndex`es, which a `Vec`'s and a slice's give. + ("slice" | "Vec", "as_ptr" | "as_mut_ptr") => { + let elem = targ(gargs, 0); + let s = arg(); + if let Some(why) = unmodeled(b, elem, "a pointer into") { + return Ok(Some(b.err(span, why))); + } + let byte = matches!(elem.kind(), ty::Uint(ty::UintTy::U8)); + match (owner, byte) { + ("slice", true) => Expr::rt("byteSliceAsPtr", vec![s]), + ("Vec", true) => Expr::rt("byteVecAsPtr", vec![s]), + _ => Expr::rt("sliceAsPtr", vec![s]), + } + } + ("ptr" | "NonNull", "add" | "sub" | "offset" | "wrapping_add" | "wrapping_sub" | "wrapping_offset") => { + let (p, n) = (arg(), arg()); + if b.t.zero_sized(pointee()) { + // Every pointer to a value of no bytes is where it was. + out.push(StmtKind::Expr(n).at(None)); + p + } else if let Some(why) = unmodeled(b, pointee(), "an offset of a pointer to") { + b.err(span, why) + } else { + let n = match name { + "sub" | "wrapping_sub" => Expr::unary("-", n), + _ => n, + }; + Expr::rt("ptrAdd", vec![p, n]) + } + } + ("ptr" | "NonNull", "offset_from" | "offset_from_unsigned") => { + let (p, origin) = (arg(), arg()); + match unmodeled(b, pointee(), "`offset_from` of pointers to") { + // std asserts that the pointee has bytes. + _ if b.t.zero_sized(pointee()) => b.err( + span, + format!("`offset_from` of pointers to `{}`, which has no bytes", pointee()), + ), + Some(why) => b.err(span, why), + None => Expr::rt("ptrOffsetFrom", vec![p, origin]), + } + } + ( + "ptr", + "copy" + | "copy_nonoverlapping" + | "copy_to" + | "copy_to_nonoverlapping" + | "copy_from" + | "copy_from_nonoverlapping", + ) + | ("NonNull", "copy_to" | "copy_to_nonoverlapping" | "copy_from" | "copy_from_nonoverlapping") => { + let (x, y, n) = (arg(), arg(), arg()); + let (src, dst) = match name { + "copy_from" | "copy_from_nonoverlapping" => (y, x), + _ => (x, y), + }; + if b.t.zero_sized(pointee()) { + out.push(StmtKind::Expr(n).at(None)); + Expr::undefined() + } else if let Some(why) = unmodeled(b, pointee(), "a copy between pointers to") { + b.err(span, why) + } else { + let mut args = vec![src, dst, n]; + args.extend(b.t.snapshot_fn(pointee(), span)); + Expr::rt("ptrCopy", args) + } + } + ("raw", "from_raw_parts" | "from_raw_parts_mut") + | ("ptr", "slice_from_raw_parts" | "slice_from_raw_parts_mut") + | ("NonNull", "slice_from_raw_parts") => { + let (p, len) = (arg(), arg()); + let slice = match unmodeled(b, pointee(), "a slice from a pointer to") { + _ if b.t.zero_sized(pointee()) => { + return Ok(Some(b.err( + span, + format!("a slice of `{}`, which has no bytes, from a pointer", pointee()), + ))); + } + Some(why) => return Ok(Some(b.err(span, why))), + None if matches!(pointee().kind(), ty::Uint(ty::UintTy::U8)) => Expr::rt("ptrByteSlice", vec![p, len]), + None => Expr::rt("ptrSlice", vec![p, len]), + }; + // A raw pointer to a slice is an `rt.Place` of it, as one to any value JS copies is. + match owner { + "raw" => slice, + _ => Expr::Object(vec![(Key::Name("v".into()), slice)]), + } + } ("mem", "forget") => Expr::undefined(), _ => return Err(args.collect()), }; Ok(Some(e)) } +/// Why pointers to `ty` can't point into an array, if they can't: an element JS keeps as +/// an object or as bytes is what a pointer to it is, which knows no array it is in. Only an +/// instance decides what a parameter is. +fn unmodeled<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>, what: &str) -> Option { + if b.t.polymorphic() && ty.has_param() { + return None; + } + match b.t.pointee(ty) { + Pointee::Place => None, + Pointee::Object | Pointee::Bytes => Some(format!( + "{what} `{ty}`, which JS keeps as an object that knows no array it is in" + )), + } +} + +/// The pointer `p` to `from`, as one to `to`: the same pointer, as one to what a +/// `MaybeUninit` holds or the other way, or the pointer to the first element of the array +/// or slice it points at. +pub fn cast<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, from: Ty<'tcx>, to: Ty<'tcx>, span: Span) -> Expr { + let (inner, into) = (b.t.uninit_inner(from), b.t.uninit_inner(to)); + if inner == into { + return p; + } + let elem = match inner.kind() { + ty::Array(elem, _) | ty::Slice(elem) if b.t.uninit_inner(*elem) == into => Some(*elem), + _ => None, + }; + let Some(elem) = elem else { + return b.err( + span, + format!("a cast from a pointer to `{from}` to one to `{to}`, which reads the bytes of one type as another"), + ); + }; + if let Some(why) = unmodeled(b, elem, "a pointer into an array of") { + return b.err(span, why); + } + // A pointer to a slice is an `rt.Place` of it, and one to an array the array, or the + // `rt.ByteSlice` of its bytes. + let s = match inner.kind() { + ty::Slice(_) => p.member("v"), + _ => p, + }; + match b.t.pointee(inner) == Pointee::Bytes || inner.is_slice() && matches!(elem.kind(), ty::Uint(ty::UintTy::U8)) { + true => Expr::rt("byteSliceAsPtr", vec![s]), + false => Expr::rt("sliceAsPtr", vec![s]), + } +} + /// What a `PartialEq` or `Clone` call's `Self` points at, if it is a raw pointer or a /// `NonNull`. fn pointer_self<'tcx>(b: &Body<'_, '_, 'tcx>, call: &Call<'tcx>) -> Option> { From 9988c747c1dc1b85813d67a22669c0f4edef1ab1 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 11:53:04 -0800 Subject: [PATCH 03/34] fix(ts): refuse a pointer to an element of an array its variable or holder replaces Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.Storage.ts | 43 ++++++++++++++++ ts/fixtures/lang/expected/elements.ts | 14 +++--- ts/fixtures/reject/census.md | 50 ++++++++++--------- ts/fixtures/reject/expected.txt | 2 + ts/fixtures/reject/src/lib.rs | 15 ++++++ ts/fixtures/reject/ts.toml | 1 + ts/src/census.rs | 1 + ts/src/lower/mir/rvalue.rs | 10 +++- 8 files changed, 105 insertions(+), 31 deletions(-) create mode 100644 ts/fixtures/lang/expected/elements.Storage.ts diff --git a/ts/fixtures/lang/expected/elements.Storage.ts b/ts/fixtures/lang/expected/elements.Storage.ts new file mode 100644 index 000000000..a4bd7973f --- /dev/null +++ b/ts/fixtures/lang/expected/elements.Storage.ts @@ -0,0 +1,43 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export class Storage { + data: rt.Place; + capacity: rt.usize; + + constructor(data: rt.Place, capacity: rt.usize) { + this.data = data; + this.capacity = capacity; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +function new_(capacity: rt.usize): Storage { + const boxed: rt.ByteSlice = rt.byteVecSlice(rt.byteVecOf(0, capacity)); + const capacity2: rt.usize = boxed.length; + return new Storage(rt.byteSliceAsPtr({ v: boxed }.v), capacity2); +} + +export function write(self: Storage, offset: rt.usize, src: rt.ByteSlice): void { + if (!(rt.usizeAdd(offset, src.length) <= self.capacity)) { + rt.panic("assertion failed: offset + src.len() <= self.capacity"); + } else { + rt.ptrCopy(rt.byteSliceAsPtr(src), rt.ptrAdd(self.data, offset), src.length); + } +} + +export function read(self: Storage, len: rt.usize): rt.ByteSlice { + return rt.ptrByteSlice(self.data, len); +} + +export function drop$ops_Drop(self: Storage): void { + (({ v: rt.ptrByteSlice(self.data, self.capacity) }.v)); +} + +export { new_ as new }; + +//# sourceMappingURL=elements.Storage.ts.map diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index 6e364146e..7dda6e4f0 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -5,7 +5,7 @@ import { copy$Option_tuple2_u8_u8, copy$array_Option_tuple2_u8_u8, drop$elements import { thenSome$03chm1q } from "./core/bool.ts"; import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1fmzefe as option_map$1fmzefe, mapOr$1nlkf9b as option_mapOr$1nlkf9b } from "./core/option.ts"; import * as Inline from "./elements.Inline.ts"; -import * as elements_Segment from "./elements.Segment.ts"; +import * as Storage from "./elements.Storage.ts"; /** * A `Vec`'s elements moved within it as `insert` and `remove` move them, and copied to @@ -91,17 +91,17 @@ export function arrays(data: rt.ByteSlice, out: rt.ByteVec): void { * freed as the slice it was. */ export function segments(data: rt.ByteSlice, out: rt.ByteVec): void { - const segment: elements_Segment.Segment = elements_Segment.new(rt.usizeAdd(data.length, 4)); - elements_Segment.write(segment, 0, data); - elements_Segment.write(segment, data.length, rt.byteSliceFrom(Uint8Array.of(1, 2, 3, 4))); - rt.byteVecExtendFromSlice(out, elements_Segment.read(segment, rt.usizeAdd(data.length, 4))); + const segment: Storage.Storage = Storage.new(rt.usizeAdd(data.length, 4)); + Storage.write(segment, 0, data); + Storage.write(segment, data.length, rt.byteSliceFrom(Uint8Array.of(1, 2, 3, 4))); + rt.byteVecExtendFromSlice(out, Storage.read(segment, rt.usizeAdd(data.length, 4))); const p: rt.Place = segment.data; rt.ptrCopy(p, rt.ptrAdd(p, 2), data.length); - rt.byteVecExtendFromSlice(out, elements_Segment.read(segment, rt.usizeAdd(data.length, 4))); + rt.byteVecExtendFromSlice(out, Storage.read(segment, rt.usizeAdd(data.length, 4))); rt.ptrCopy(rt.ptrAdd(p, 3), rt.ptrAdd(p, 1), data.length); const _36: rt.Place = rt.ptrAdd(p, rt.usizeAdd(data.length, 3)); _36.v = _36.v ^ 255; - rt.byteVecExtendFromSlice(out, elements_Segment.read(segment, rt.usizeAdd(data.length, 4))); + rt.byteVecExtendFromSlice(out, Storage.read(segment, rt.usizeAdd(data.length, 4))); const v: rt.ByteVec = rt.byteSliceToByteVec(data); rt.byteVecPush(v, 9); const a: Uint8Array = rt.bytes(4); diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index ce4fb0c79..2e232f79c 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,16 +1,16 @@ # Census of `reject-fixture` -- Refusals: 224 -- Function instances: 273 +- Refusals: 226 +- Function instances: 274 - Generic functions no instance reached, lowered over their type parameters: 13 ## By construct | Count | Construct | First | | ---: | --- | --- | -| 45 | a raw pointer to `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:603:10 | +| 46 | a raw pointer to `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:603:10 | | 21 | unsupported call to `_` | fixtures/reject/src/lib.rs:160:8 | -| 19 | a raw pointer to `_` in a variable assigned again while it may point there | fixtures/reject/src/lib.rs:611:10 | +| 20 | a raw pointer to `_` in a variable assigned again while it may point there | fixtures/reject/src/lib.rs:611:10 | | 15 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:1332:33 | | 15 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | fixtures/reject/src/lib.rs:1650:18 | | 5 | `_` with a mutable reference to a value JS copies, which its mapping would copy | fixtures/reject/src/lib.rs:1230:22 | @@ -45,13 +45,13 @@ | 1 | `_` is not supported: its field `_` is `_`, typed `_`, whose `Pending` would be `rt.OuterPending` as `_` | fixtures/reject/src/lib.rs:497:2 | | 1 | `_` of `_` by `_`, which is not supported yet | fixtures/reject/src/lib.rs:253:6 | | 1 | `_` of `_`, which is not supported yet | fixtures/reject/src/lib.rs:533:4 | -| 1 | `_` of pointers to `()`, which has no bytes | fixtures/reject/src/lib.rs:2167:13 | -| 1 | `_` of pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2160:36 | | 1 | `_`, which is not supported yet | fixtures/reject/src/lib.rs:261:9 | | 1 | `_`, which reads a capacity that depends on whether the other handles of its storage dropped, which is not supported | fixtures/reject/src/lib.rs:383:6 | | 1 | `clone_from` of `_`, whose items' `clone_from` is their own, which is not supported yet | fixtures/reject/src/lib.rs:2113:4 | | 1 | `collect` of `_`, which sorts by `_`'s own `Ord`, of entries that drop, which is not supported yet | fixtures/reject/src/lib.rs:950:22 | | 1 | `fmt::Arguments` with a placeholder, or not made in place, written to `_`'s own `fmt::Write`, which is not supported yet: std splits what a placeholder writes into `write_str` calls the runtime does not make | fixtures/reject/src/lib.rs:55:10 | +| 1 | `offset_from` of pointers to `()`, which has no bytes | fixtures/reject/src/lib.rs:2167:13 | +| 1 | `offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2160:36 | | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | fixtures/reject/src/lib.rs:209:40 | | 1 | a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet | fixtures/reject/src/lib.rs:202:8 | | 1 | a cast from `_` to `_` | fixtures/reject/src/lib.rs:68:2 | @@ -120,12 +120,12 @@ The type or module each refused type or path belongs to. | 2 | `Poll` | fixtures/reject/src/lib.rs:72:20 | | 2 | `Reused` | fixtures/reject/src/lib.rs:21:4 | | 2 | `Scaled` | fixtures/reject/src/lib.rs:459:23 | +| 2 | `[u32; 3]` | fixtures/reject/src/lib.rs:2183:10 | | 2 | `[u8; 2]` | fixtures/reject/src/lib.rs:1721:37 | | 2 | `[u8; 3]` | fixtures/reject/src/lib.rs:1721:15 | | 2 | `dyn Scaled` | fixtures/reject/src/lib.rs:459:23 | | 2 | `dyn std::any::Any` | fixtures/reject/src/lib.rs:527:32 | | 2 | `dyn std::iter::Iterator` | fixtures/reject/src/lib.rs:11:16 | -| 2 | `offset_from` | fixtures/reject/src/lib.rs:2160:36 | | 2 | `slice` | fixtures/reject/src/lib.rs:21:4 | | 2 | `std::any::TypeId` | fixtures/reject/src/lib.rs:533:2 | | 2 | `std::collections::BTreeSet` | fixtures/reject/src/lib.rs:235:75 | @@ -191,6 +191,7 @@ The type or module each refused type or path belongs to. | 1 | `InGeneric` | fixtures/reject/src/lib.rs:1317:9 | | 1 | `InNested` | fixtures/reject/src/lib.rs:1305:9 | | 1 | `Lane` | fixtures/reject/src/lib.rs:867:19 | +| 1 | `Lanes` | fixtures/reject/src/lib.rs:2185:10 | | 1 | `Loaded` | fixtures/reject/src/lib.rs:1137:29 | | 1 | `Looped` | fixtures/reject/src/lib.rs:1105:26 | | 1 | `Mapped` | fixtures/reject/src/lib.rs:1182:39 | @@ -287,8 +288,6 @@ The type or module each refused type or path belongs to. | 1 | `_` of `_`, which is not supported yet | `std::any::Any::type_id`, `dyn std::any::Any` | fixtures/reject/src/lib.rs:533:4 | | 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `slice::clone_from_slice`, `Reused` | fixtures/reject/src/lib.rs:286:4 | | 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `slice::fill`, `Reused` | fixtures/reject/src/lib.rs:21:4 | -| 1 | `_` of pointers to `()`, which has no bytes | `offset_from` | fixtures/reject/src/lib.rs:2167:13 | -| 1 | `_` of pointers to `_`, which JS keeps as an object that knows no array it is in | `offset_from`, `(u8, u8)` | fixtures/reject/src/lib.rs:2160:36 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::collections::LinkedList::retain` | fixtures/reject/src/lib.rs:2112:4 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::default::Default::default` | fixtures/reject/src/lib.rs:2114:2 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::iter::once` | fixtures/reject/src/lib.rs:1230:22 | @@ -299,6 +298,7 @@ The type or module each refused type or path belongs to. | 1 | `clone_from` of `_`, whose items' `clone_from` is their own, which is not supported yet | `std::collections::LinkedList` | fixtures/reject/src/lib.rs:2113:4 | | 1 | `collect` of `_`, which sorts by `_`'s own `Ord`, of entries that drop, which is not supported yet | `std::collections::BTreeMap`, `std::cell::Cell` | fixtures/reject/src/lib.rs:950:22 | | 1 | `fmt::Arguments` with a placeholder, or not made in place, written to `_`'s own `fmt::Write`, which is not supported yet: std splits what a placeholder writes into `write_str` calls the runtime does not make | `Sink` | fixtures/reject/src/lib.rs:55:10 | +| 1 | `offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2160:36 | | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | `std::iter::Map` | fixtures/reject/src/lib.rs:209:40 | | 1 | a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet | `std::iter::Zip` | fixtures/reject/src/lib.rs:202:8 | | 1 | a cast from `_` to `_` | `u64`, `f16` | fixtures/reject/src/lib.rs:68:2 | @@ -329,6 +329,7 @@ The type or module each refused type or path belongs to. | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByKeyTake`, `(u16, ByKeyTake)` | fixtures/reject/src/lib.rs:1623:52 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByRefReplace`, `(u16, ByRefReplace)` | fixtures/reject/src/lib.rs:1592:32 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByRefTake`, `(u16, ByRefTake)` | fixtures/reject/src/lib.rs:1600:32 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `[u32; 3]`, `Lanes` | fixtures/reject/src/lib.rs:2185:10 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `Aliased` | fixtures/reject/src/lib.rs:1115:29 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `ByBox` | fixtures/reject/src/lib.rs:1499:35 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `ByClosure` | fixtures/reject/src/lib.rs:1514:35 | @@ -357,6 +358,7 @@ The type or module each refused type or path belongs to. | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `Repeated` | fixtures/reject/src/lib.rs:1216:18 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `Rewritten` | fixtures/reject/src/lib.rs:1125:47 | | 1 | a raw pointer to `_` in a variable assigned again while it may point there | `Target` | fixtures/reject/src/lib.rs:1664:10 | +| 1 | a raw pointer to `_` in a variable assigned again while it may point there | `[u32; 3]` | fixtures/reject/src/lib.rs:2183:10 | | 1 | a raw pointer to `_` in an element, a payload, or a union field, which is not supported yet | `(u32, u32)` | fixtures/reject/src/lib.rs:674:10 | | 1 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | `InArray`, `[InArray; 2]` | fixtures/reject/src/lib.rs:1332:33 | | 1 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | `InTuple`, `(u16, InTuple)` | fixtures/reject/src/lib.rs:1341:33 | @@ -431,7 +433,7 @@ The type or module each refused type or path belongs to. ## Spans -
a raw pointer to `_` in `_`, which the crate may put another object in place of (45) +
a raw pointer to `_` in `_`, which the crate may put another object in place of (46) - fixtures/reject/src/lib.rs:603:10: a raw pointer to `(u32, u32)` in `Span2`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:664:2: a raw pointer to `(u32, u32)` in `Window`, which the crate may put another object in place of @@ -478,6 +480,7 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:1608:51: a raw pointer to `ByKeySet` in `(u16, ByKeySet)`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:1616:55: a raw pointer to `ByKeyReplace` in `(u16, ByKeyReplace)`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:1623:52: a raw pointer to `ByKeyTake` in `(u16, ByKeyTake)`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2185:10: a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of
@@ -507,7 +510,7 @@ The type or module each refused type or path belongs to.
-
a raw pointer to `_` in a variable assigned again while it may point there (19) +
a raw pointer to `_` in a variable assigned again while it may point there (20) - fixtures/reject/src/lib.rs:611:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there - fixtures/reject/src/lib.rs:619:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there @@ -528,6 +531,7 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:1692:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there - fixtures/reject/src/lib.rs:1728:26: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there - fixtures/reject/src/lib.rs:1729:10: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2183:10: a raw pointer to `[u32; 3]` in a variable assigned again while it may point there
@@ -799,18 +803,6 @@ The type or module each refused type or path belongs to.
-
`_` of pointers to `()`, which has no bytes (1) - -- fixtures/reject/src/lib.rs:2167:13: `offset_from` of pointers to `()`, which has no bytes - -
- -
`_` of pointers to `_`, which JS keeps as an object that knows no array it is in (1) - -- fixtures/reject/src/lib.rs:2160:36: `offset_from` of pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in - -
-
`_`, which is not supported yet (1) - fixtures/reject/src/lib.rs:261:9: `Hasher::write`, which is not supported yet @@ -841,6 +833,18 @@ The type or module each refused type or path belongs to.
+
`offset_from` of pointers to `()`, which has no bytes (1) + +- fixtures/reject/src/lib.rs:2167:13: `offset_from` of pointers to `()`, which has no bytes + +
+ +
`offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2160:36: `offset_from` of pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+
a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet (1) - fixtures/reject/src/lib.rs:209:40: a `collect` in place of `std::iter::Map, {closure@fixtures/reject/src/lib.rs:208:32: 208:40}>, std::ops::Range>, {closure@fixtures/reject/src/lib.rs:209:24: 209:32}>`, a zip std reads by index, holding values that drop, which is not supported yet diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 8f482cc3c..2368bbf7e 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -205,6 +205,8 @@ a pointer into an array of `(u8, u8)`, which JS keeps as an object that knows no `offset_from` of pointers to `()`, which has no bytes at fixtures/reject/src/lib.rs:2167:13 a slice of `()`, which has no bytes, from a pointer at fixtures/reject/src/lib.rs:2167:39 a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2172:13 +a raw pointer to `[u32; 3]` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2183:10 +a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2185:10 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:125:2 an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet at fixtures/reject/src/lib.rs:145:1 overriding `Iterator::size_hint`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:173:2 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index f282f4216..c64775cca 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -2171,3 +2171,18 @@ pub fn unit_elements(p: *const ()) -> usize { pub fn element_to_array(v: &[u32]) -> u32 { unsafe { (*(v.as_ptr() as *const [u32; 2]))[1] } } + +pub struct Lanes { + pub lanes: [u32; 3], +} + +/// Pointers to elements of arrays that a later assignment gives another JS array, which a +/// pointer into the first would not see: a variable's, and a struct's field. +pub fn elements_replaced(n: u32, l: &mut Lanes) -> u32 { + let mut a = [n; 3]; + let p = &raw mut a[1]; + a = [0; 3]; + let q = &raw mut l.lanes[2]; + l.lanes = [1; 3]; + unsafe { *p + *q + a[0] } +} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 9d2b70c1d..cc35da3b7 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -168,5 +168,6 @@ roots = [ "object_elements", "unit_elements", "element_to_array", + "elements_replaced", ] printable = ["Meters"] diff --git a/ts/src/census.rs b/ts/src/census.rs index 42392d27e..d105439b6 100644 --- a/ts/src/census.rs +++ b/ts/src/census.rs @@ -187,6 +187,7 @@ const VERBATIM: &[&str] = &[ "let ... else", "match", "next", + "offset_from", "ref mut", "rt.None", "rt.OuterPending", diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index c968ce443..fc70972bc 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -117,9 +117,17 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // A pointer to an element is where it is in its array, which pointer // arithmetic moves within. [ - .., + array @ .., mir::ProjectionElem::Index(_) | mir::ProjectionElem::ConstantIndex { .. }, ] => { + // It points into the array, which must stay where it is. + let array = Place { + local: place.local, + projection: tcx.mk_place_elems(array), + }; + if let Some(why) = self.out_of_step(array, false) { + return Some(self.err(span, why)); + } let target = self.place_expr(*place, span, out); let target = self.stable_target(*place, target, span, out); match target.unlocated() { From 2d8439ea555a1f810f1853754315d423a953f260 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:14:54 -0800 Subject: [PATCH 04/34] chore(ts): refresh the lang harness snapshot after merging main Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/harness.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 822bfdfa9..17a744373 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -274,14 +274,14 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) capacity_deques(a0153, a1153.data, a1153.start, a1153.length, a2153); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::heaps", (a0154: rt.u8, a1154: rt.ByteSlice, a2154: rt.ByteVec): undefined => { capacity_heaps(a0154, a1154.data, a1154.start, a1154.length, a2154); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::vecs", (_2193: rt.u8, data93: rt.ByteSlice, out94: rt.ByteVec): undefined => { - elements_vecs(data93, out94); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::arrays", (_2194: rt.u8, data94: rt.ByteSlice, out95: rt.ByteVec): undefined => { - elements_arrays(data94, out95); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::segments", (_2195: rt.u8, data95: rt.ByteSlice, out96: rt.ByteVec): undefined => { - elements_segments(data95, out96); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::inline", (_2196: rt.u8, data96: rt.ByteSlice, out97: rt.ByteVec): undefined => { - inline(data96, out97); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::vecs", (_2196: rt.u8, data96: rt.ByteSlice, out97: rt.ByteVec): undefined => { + elements_vecs(data96, out97); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::arrays", (_2197: rt.u8, data97: rt.ByteSlice, out98: rt.ByteVec): undefined => { + elements_arrays(data97, out98); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::segments", (_2198: rt.u8, data98: rt.ByteSlice, out99: rt.ByteVec): undefined => { + elements_segments(data98, out99); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::inline", (_2199: rt.u8, data99: rt.ByteSlice, out100: rt.ByteVec): undefined => { + inline(data99, out100); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } From f2ed825ee4387fd3ac574e0bf1a834a3a838e31e Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:37:00 -0800 Subject: [PATCH 05/34] fix(ts): keep element pointers exact at wrapped addresses, lone values, views, and empty slices Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 2 +- ts/fixtures/lang/expected/elements.Storage.ts | 4 +- ts/fixtures/lang/expected/elements.ts | 51 ++++++++- ts/fixtures/lang/expected/harness.ts | 4 +- ts/fixtures/lang/src/elements.rs | 50 ++++++++- ts/fixtures/lang/src/harness.rs | 1 + ts/runtime/rt.ts | 104 ++++++++++++++---- ts/runtime/rt/collections.ts | 18 ++- ts/src/lower/mir/rvalue.rs | 7 +- ts/src/lower/pointer.rs | 13 ++- 10 files changed, 214 insertions(+), 40 deletions(-) diff --git a/ts/README.md b/ts/README.md index 36fc0f382..961daa8dc 100644 --- a/ts/README.md +++ b/ts/README.md @@ -101,7 +101,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | tuple, `[T; N]`, `&[T]`, `Box<[T]>`, `Vec` | an array; for bytes, `Uint8Array` for `[u8; N]`, `rt.ByteSlice` for `&[u8]`, `&[u8; N]`, and `Box<[u8]>`, and `rt.ByteVec` for `Vec` | | `Cow<[T]>` | `rt.Cow`: the `Vec` it owns, or an `rt.Borrowed` holding the slice it borrows; `rt.ByteCow` for bytes, an `rt.ByteVec` or an `rt.ByteSlice` | | `&T`, `&mut T`, `Box`, `Reverse`, `NonZero` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, a `String`, or a reference, or to an `Arc` or a `Waker`, is an `rt.Place`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rt.AtIndex` of its element, which `add`, `sub`, `offset`, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view, and which a pointer to a value in no array fails at run time to move; a pointer into an array of values JS keeps as objects is refused; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | +| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rt.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view; one to a value in no array is element 0 of an array of one, as in Rust, and a read or write through one past the array JS keeps it in fails at run time, as past a shared subslice, which JS copies; a pointer into an array of values JS keeps as objects is refused; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | | `MaybeUninit` | `T`: `undefined` until written, or an object whose parts kept as objects are there, holding `undefined` | | `union` | an object of slots named by offset, `at0` and `at8`, each holding the field part kept there; fields must overlap in the same type, or in a non-null pointer and an `Option` of it | | an iterator | `rt.Iter`, whose adaptors are runtime functions: `rt.iterMap(rt.iterSlice(v), f)` | diff --git a/ts/fixtures/lang/expected/elements.Storage.ts b/ts/fixtures/lang/expected/elements.Storage.ts index a4bd7973f..17dc286e2 100644 --- a/ts/fixtures/lang/expected/elements.Storage.ts +++ b/ts/fixtures/lang/expected/elements.Storage.ts @@ -31,11 +31,11 @@ export function write(self: Storage, offset: rt.usize, src: rt.ByteSlice): void } export function read(self: Storage, len: rt.usize): rt.ByteSlice { - return rt.ptrByteSlice(self.data, len); + return rt.ptrByteSlice(self.data, len, false); } export function drop$ops_Drop(self: Storage): void { - (({ v: rt.ptrByteSlice(self.data, self.capacity) }.v)); + (({ v: rt.ptrByteSlice(self.data, self.capacity, true) }.v)); } export { new_ as new }; diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index 7dda6e4f0..de1ca6481 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -27,9 +27,9 @@ export function vecs(data: rt.ByteSlice, out: rt.ByteVec): void { emit(rt.ptrSlice(p, len), out); const end: rt.Place = rt.ptrAdd(p, len); const last: rt.Place = rt.ptrAdd(end, -1); - const _54: rt.Place = rt.ptrAdd(last, -1); + const _54: rt.Place = rt.ptrWrappingAdd(last, -1, 4); _54.v = rt.u32Add(_54.v, 1); - const _56: rt.Place = rt.ptrAdd(rt.ptrAdd(p, 1), -1); + const _56: rt.Place = rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, 1, 4), -1, 4); _56.v = rt.u32Add(_56.v, 5); rt.byteVecPush(out, rt.ptrOffsetFrom(end, p) & 0xff); rt.byteVecPush(out, rt.ptrOffsetFrom(p, last) & 0xff); @@ -56,7 +56,7 @@ export function arrays(data: rt.ByteSlice, out: rt.ByteVec): void { if (!(i < 5)) { rt.panic("index out of bounds: the len is " + 5 + " but the index is " + i); } - const at: rt.Place = new rt.AtIndex(a, i); + const at: rt.Place = rt.sliceAsPtr(a, i); at.v = 99; const whole: rt.Place = rt.sliceAsPtr(a); const _18: rt.Place = rt.ptrAdd(whole, rt.usizeAdd(i, 1)); @@ -111,8 +111,8 @@ export function segments(data: rt.ByteSlice, out: rt.ByteVec): void { rt.ptrCopy(p2, rt.byteSliceAsPtr(rt.byteSliceFrom(a)), rt.min(v.length, 4)); const byte: rt.Place = rt.ptrAdd(rt.byteSliceAsPtr(rt.byteSliceFrom(a)), 1); rt.ptrAdd(byte, 2).v = 85; - rt.byteVecExtendFromSlice(out, rt.ptrByteSlice(rt.ptrAdd(byte, -1), 4)); - const one: rt.Place = new rt.AtIndex(a, 2); + rt.byteVecExtendFromSlice(out, rt.ptrByteSlice(rt.ptrAdd(byte, -1), 4, false)); + const one: rt.Place = rt.sliceAsPtr(a, 2); one.v = one.v ^ 15; rt.byteVecPush(out, one.v); rt.iterExtendBytes(out, rt.iterSlice(rt.byteVecView(v)), undefined, true); @@ -198,6 +198,47 @@ export function inline(data: rt.ByteSlice, out: rt.ByteVec): void { shared[Symbol.dispose](); } +/** + * Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as + * the array of one Rust treats it as, and into a mutable part of an array, measured from + * the array, and the pointer an empty slice keeps. + */ +export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { + for (const n of rt.byteSliceView(data)) { + const bytes: Uint8Array = Uint8Array.of(n, 1, 2, 3); + const p: rt.Place = rt.byteSliceAsPtr(rt.byteSliceFrom(bytes)); + const q: rt.Place = rt.sliceAsPtr([n as rt.u16, 7, 8]); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, 4294967295, 1), 1, 1).v); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, 2147483648, 1), 2147483648, 1).v); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, -4294967295, 1), -1, 1).v); + rt.iterExtendBytes(out, rt.iterSlice(rt.u16ToBeBytes(rt.ptrWrappingAdd(rt.ptrWrappingAdd(q, 2147483648, 2), 1, 2).v).reverse()), undefined, true); + rt.iterExtendBytes(out, rt.iterSlice(rt.u16ToBeBytes(rt.ptrWrappingAdd(rt.ptrWrappingAdd(rt.ptrWrappingAdd(q, 3221225472, 2), 1 << 30, 2), 2, 2).v).reverse()), undefined, true); + let lone: rt.u32 = rt.u32Add(n as rt.u32, 42); + const at: rt.Place = rt.place(() => lone, (value) => lone = value); + const _63: rt.u32[] = rt.ptrSlice(at, 1); + rt.idx(_63, 0); + rt.byteVecPush(out, _63[0] & 0xff); + const _68: rt.u32[] = rt.ptrSlice(at, 1); + rt.idx(_68, 0); + _68[0] = rt.u32Add(_68[0], 1); + rt.byteVecPush(out, rt.ptrAdd(rt.ptrAdd(at, 1), -1).v & 0xff); + rt.byteVecPush(out, rt.ptrOffsetFrom(rt.ptrAdd(at, 1), at) & 0xff); + rt.byteVecPush(out, lone & 0xff); + const a: rt.u32[] = [n as rt.u32, 1, 2, 3]; + const base: rt.Place = rt.sliceAsPtr(a); + const part: rt.u32[] = rt.sliceRangeMut(a, 1, 3); + rt.idx(part, 1); + const at2: rt.Place = rt.sliceAsPtr(part, 1); + rt.byteVecPush(out, rt.ptrOffsetFrom(at2, base) & 0xff); + at2.v = rt.u32Add(at2.v, 5); + rt.byteVecPush(out, rt.ptrOffsetFrom(rt.sliceAsPtr(rt.ptrSlice(rt.ptrAdd(at2, 1), 0)), base) & 0xff); + rt.byteVecPush(out, rt.ptrOffsetFrom(rt.byteSliceAsPtr(rt.ptrByteSlice(rt.ptrAdd(rt.byteSliceAsPtr(rt.byteSliceFrom(bytes)), 2), 0, false)), p) & 0xff); + rt.iterExtendBytes(out, rt.iterMap(rt.iterSlice(a), (_2: rt.u32): rt.u8 => { + return _2 & 0xff; + }), undefined, true); + } +} + export function emit(values: rt.u32[], out: rt.ByteVec): void { rt.iterExtendBytes(out, rt.iterFlatMap(rt.iterSlice(values), (x: rt.u32): Uint8Array => { return rt.u32ToBeBytes(x).reverse(); diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 17a744373..3ced07162 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -23,7 +23,7 @@ import { defaults as defaults_defaults } from "./defaults.ts"; import { durations } from "./durations.ts"; import { dyns, shared as dyns_shared, anys, fns } from "./dyns.ts"; import { edits as edits_edits, bytes as edits_bytes } from "./edits.ts"; -import { vecs as elements_vecs, arrays as elements_arrays, segments as elements_segments, inline } from "./elements.ts"; +import { vecs as elements_vecs, arrays as elements_arrays, segments as elements_segments, inline, edges as elements_edges } from "./elements.ts"; import { eqs } from "./errors.ts"; import { fields as fields_fields } from "./fields.ts"; import { floats, mulAdds as floats_mulAdds, formats as floats_formats, parses as floats_parses } from "./floats.ts"; @@ -282,6 +282,8 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) elements_segments(data98, out99); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::inline", (_2199: rt.u8, data99: rt.ByteSlice, out100: rt.ByteVec): undefined => { inline(data99, out100); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::edges", (_2200: rt.u8, data100: rt.ByteSlice, out101: rt.ByteVec): undefined => { + elements_edges(data100, out101); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 9deda0288..04db37c83 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -2,7 +2,8 @@ //! `MaybeUninit` array's elements, moved by `add`, `sub`, `offset`, and their wrapping //! forms, measured by `offset_from`, copied between by `ptr::copy` and //! `copy_nonoverlapping`, overlapping either way, and made slices again, as `smallvec`'s -//! inline storage and moq-net's frame segments use them. +//! inline storage and moq-net's frame segments use them, and the edges: addresses that +//! wrap, a value in no array, and a slice of none. use std::mem::MaybeUninit; use std::ptr::{self, NonNull}; @@ -251,6 +252,53 @@ pub fn inline(data: &[u8], out: &mut Vec) { out.extend(src.iter().chain(&dst).flat_map(|p| p.map_or([0, 0], |(a, b)| [a, b]))); } +/// Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as +/// the array of one Rust treats it as, and into a mutable part of an array, measured from +/// the array, and the pointer an empty slice keeps. +pub fn edges(data: &[u8], out: &mut Vec) { + for &n in data { + let bytes = [n, 1, 2, 3]; + let p = bytes.as_ptr(); + let wide = [u16::from(n), 7, 8]; + let q = wide.as_ptr(); + unsafe { + out.push(*p.wrapping_add(usize::MAX).wrapping_add(1)); + out.push(*p.wrapping_add(1 << 31).wrapping_add(1 << 31)); + out.push(*p.wrapping_sub(usize::MAX).wrapping_offset(-1)); + out.extend(q.wrapping_add(1 << 31).wrapping_add(1).read().to_le_bytes()); + out.extend( + q.wrapping_add(3 << 30) + .wrapping_add(1 << 30) + .wrapping_add(2) + .read() + .to_le_bytes(), + ); + } + let mut lone = u32::from(n) + 42; + let at = &raw mut lone; + unsafe { + out.push(slice::from_raw_parts(at, 1)[0] as u8); + slice::from_raw_parts_mut(at, 1)[0] += 1; + out.push(at.add(1).sub(1).read() as u8); + out.push(at.add(1).offset_from(at) as u8); + } + out.push(lone as u8); + let mut a = [u32::from(n), 1, 2, 3]; + let base = a.as_mut_ptr(); + let part = &mut a[1..3]; + let at = &raw mut part[1]; + unsafe { + out.push(at.offset_from(base) as u8); + *at += 5; + let empty = slice::from_raw_parts(at.add(1), 0); + out.push(empty.as_ptr().offset_from(base) as u8); + let none = slice::from_raw_parts(bytes.as_ptr().add(2), 0); + out.push(none.as_ptr().offset_from(p) as u8); + } + out.extend(a.iter().map(|&x| x as u8)); + } +} + fn emit(values: &[u32], out: &mut Vec) { out.extend(values.iter().flat_map(|x| x.to_le_bytes())); } diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 258fe384d..5599c7da0 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -286,6 +286,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("elements::arrays", |_, data, out| elements::arrays(data, out)), ("elements::segments", |_, data, out| elements::segments(data, out)), ("elements::inline", |_, data, out| elements::inline(data, out)), + ("elements::edges", |_, data, out| elements::edges(data, out)), ] } diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 20161959d..46b333d6a 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -144,7 +144,7 @@ export type VecOf = T[] | ByteVec; export type CowOf = Cow | ByteCow; /** The pointer to element `index` of `s`, a slice, a `[T; N]`, or a `Vec`'s storage. */ -export function sliceAsPtr(s: T[], index = 0): Place { +export function sliceAsPtr(s: { [i: number]: T }, index = 0): Place { const view = (s as { [VIEW]?: View })[VIEW]; return view ? new AtIndex(view.s, view.start + index) : new AtIndex(s, index); } @@ -159,24 +159,85 @@ export function byteVecAsPtr(v: ByteVec): Place { return new AtIndex(v.data, 0); } -/** The array and index `p` points at, which only a pointer into an array has. */ -function elemOf(p: Place, what: string): AtIndex { +/** The array of one element that a pointer to a value in no array points into, by its place. */ +const LONE = /* @__PURE__ */ new WeakMap, unknown[]>(); + +/** The traps of an array of one element, the value at `p`. */ +class Lone implements ProxyHandler { + readonly p: Place; + + constructor(p: Place) { + this.p = p; + } + + get(target: T[], key: string | symbol): unknown { + return key === "0" ? this.p.v : Reflect.get(target, key); + } + + set(target: T[], key: string | symbol, value: T): boolean { + if (key !== "0") return Reflect.set(target, key, value); + this.p.v = value; + return true; + } + + has(target: T[], key: string | symbol): boolean { + return key === "0" || Reflect.has(target, key); + } + + getOwnPropertyDescriptor(target: T[], key: string | symbol): PropertyDescriptor | undefined { + if (key !== "0") return Reflect.getOwnPropertyDescriptor(target, key); + return { value: this.p.v, writable: true, enumerable: true, configurable: true }; + } +} + +/** + * Where `p` points, as an element of an array: its own, or for a pointer to a value in no + * array, element 0 of an array of one that reads and writes it, as Rust treats one. + */ +function elemOf(p: Place): AtIndex { if (p instanceof AtIndex) return p; - throw new Error(`rust2-ts: ${what} of a pointer to a value in no array`); + let lone = LONE.get(p) as T[] | undefined; + if (!lone) { + lone = new Proxy(new Array(1), new Lone(p)); + LONE.set(p, lone); + } + return new AtIndex(lone, 0); } -/** `p.add(n)`, `p.offset(n)`, and their wrapping forms: `n` elements past where `p` points. */ +/** + * The index `n` elements of `size` bytes past `index`, where wasm32's 32-bit addresses + * wrap: two indices whose addresses differ by a multiple of 2^32 are the same. + */ +function wrapIndex(index: number, n: number, size: number): number { + // The addresses of indices a period apart are the same. + const low = size & -size; + const period = low ? 2 ** 32 / low : 2 ** 32; + const at = (index + n) % period; + return at >= period / 2 ? at - period : at < -period / 2 ? at + period : at; +} + +/** `p.add(n)` and `p.offset(n)`: `n` elements past where `p` points. */ export function ptrAdd(p: Place, n: number): Place { if (n === 0) return p; - const at = elemOf(p, "an offset"); + const at = elemOf(p); return new AtIndex(at.array, at.index + n); } +/** + * `p.wrapping_add(n)` and `wrapping_offset`, `n` an offset of `size`-byte elements read + * as wasm32's `isize`, wrapping as a 32-bit address does. + */ +export function ptrWrappingAdd(p: Place, n: number, size: number): Place { + if (n === 0) return p; + const at = elemOf(p); + return new AtIndex(at.array, wrapIndex(at.index, n | 0, size)); +} + /** `p.offset_from(origin)`: how many elements `p` is past `origin`, in the same array. */ export function ptrOffsetFrom(p: Place, origin: Place): number { if (p === origin) return 0; - const [a, b] = [elemOf(p, "`offset_from`"), elemOf(origin, "`offset_from`")]; - if (a.array !== b.array) throw new Error("rust2-ts: `offset_from` of pointers into different arrays"); + const [a, b] = [elemOf(p), elemOf(origin)]; + if (a.array !== b.array) throw new Error("rust2-ts: `offset_from` of pointers JS keeps in different arrays"); return a.index - b.index; } @@ -186,11 +247,7 @@ export function ptrOffsetFrom(p: Place, origin: Place): number { */ export function ptrCopy(src: Place, dst: Place, n: number, copy?: (value: T) => T): void { if (n === 0) return; - if (n === 1 && !(src instanceof AtIndex && dst instanceof AtIndex)) { - dst.v = copy ? copy(src.v) : src.v; - return; - } - const [from, to] = [elemOf(src, "a copy"), elemOf(dst, "a copy")]; + const [from, to] = [elemOf(src), elemOf(dst)]; if (ArrayBuffer.isView(from.array) && ArrayBuffer.isView(to.array)) { const bytes = from.array as unknown as Uint8Array; (to.array as unknown as Uint8Array).set(bytes.subarray(from.index, from.index + n), to.index); @@ -202,17 +259,24 @@ export function ptrCopy(src: Place, dst: Place, n: number, copy?: (valu /** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` elements from `p`. */ export function ptrSlice(p: Place, len: number): T[] { - if (len === 0) return []; - const at = elemOf(p, "a slice"); + // One of none from a dangling pointer points nowhere. + if (len === 0 && !(p instanceof AtIndex)) return []; + const at = elemOf(p); const array = at.array as T[]; return at.index === 0 && len === array.length ? array : sliceView(array, at.index, at.index + len); } -/** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` bytes from `p`. */ -export function ptrByteSlice(p: Place, len: number): ByteSlice { - if (len === 0) return new ByteSlice(new Uint8Array(0), 0, 0); - const at = elemOf(p, "a slice"); - return new ByteSlice(at.array as Uint8Array, at.index, len); +/** + * `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` bytes from `p`, which + * may be `mutable` only if they are in an array of bytes. + */ +export function ptrByteSlice(p: Place, len: number, mutable: boolean): ByteSlice { + if (len === 0 && !(p instanceof AtIndex)) return new ByteSlice(new Uint8Array(0), 0, 0); + const at = elemOf(p); + if (at.array instanceof Uint8Array) return new ByteSlice(at.array, at.index, len); + // A byte in no array, which no `Uint8Array` can share. + if (mutable) throw new Error("rust2-ts: a mutable slice of a byte in no array of bytes"); + return new ByteSlice(Uint8Array.of(p.v), 0, len); } /** diff --git a/ts/runtime/rt/collections.ts b/ts/runtime/rt/collections.ts index 5189256d8..7784fdbd0 100644 --- a/ts/runtime/rt/collections.ts +++ b/ts/runtime/rt/collections.ts @@ -372,16 +372,26 @@ export class AtIndex implements Place { } get v(): T { + this.check(); return this.array[this.index]; } set v(value: T) { - // A JS array grows by a write past its end, where a `Vec`'s length stays until `set_len`. - if (Array.isArray(this.array) && this.index >= this.array.length) { - throw new Error("rust2-ts: a write through a pointer past the end of an array"); - } + this.check(); this.array[this.index] = value; } + + /** + * Fails past either end of the array, which JS doesn't keep Rust's memory around: a JS + * array grows by a write past its end, where a `Vec`'s length stays until `set_len`, and + * a shared subslice is a copy, which a pointer moved past it would read nothing of. + */ + private check(): void { + const length = (this.array as ArrayLike).length; + if (this.index < 0 || this.index >= length) { + throw new Error("rust2-ts: a read or write through a pointer past the array JS keeps it in"); + } + } } /** The place of `k`'s value in `m`, which holds it. */ diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index fc70972bc..35f362754 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -131,10 +131,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let target = self.place_expr(*place, span, out); let target = self.stable_target(*place, target, span, out); match target.unlocated() { - Expr::Index(array, index) => Expr::New( - Box::new(Expr::Rt("AtIndex".into())), - vec![*array.clone(), *index.clone()], - ), + Expr::Index(array, index) => { + Expr::rt("sliceAsPtr", vec![*array.clone(), *index.clone()]) + } _ => self.place_of(target, None), } } diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 0e5bbe8fc..c71381990 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -648,7 +648,13 @@ pub fn call<'tcx>( "sub" | "wrapping_sub" => Expr::unary("-", n), _ => n, }; - Expr::rt("ptrAdd", vec![p, n]) + match name.starts_with("wrapping") { + true => { + let size = super::iter::size_of(b, pointee(), span); + Expr::rt("ptrWrappingAdd", vec![p, n, size]) + } + false => Expr::rt("ptrAdd", vec![p, n]), + } } } ("ptr" | "NonNull", "offset_from" | "offset_from_unsigned") => { @@ -701,7 +707,10 @@ pub fn call<'tcx>( ))); } Some(why) => return Ok(Some(b.err(span, why))), - None if matches!(pointee().kind(), ty::Uint(ty::UintTy::U8)) => Expr::rt("ptrByteSlice", vec![p, len]), + None if matches!(pointee().kind(), ty::Uint(ty::UintTy::U8)) => { + let mutable = !matches!(name, "from_raw_parts" | "slice_from_raw_parts"); + Expr::rt("ptrByteSlice", vec![p, len, Expr::lit(mutable.to_string())]) + } None => Expr::rt("ptrSlice", vec![p, len]), }; // A raw pointer to a slice is an `rt.Place` of it, as one to any value JS copies is. From ca0d3aa1c5df4817a92f2d4e30eb223497d3cee8 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 13:00:20 -0800 Subject: [PATCH 06/34] fix(ts): copy only a shared slice of a lone byte, and keep a lone value's pointer in an empty slice Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.Storage.ts | 4 ++-- ts/fixtures/lang/expected/elements.ts | 9 +++++++-- ts/fixtures/lang/src/elements.rs | 3 +++ ts/runtime/rt.ts | 15 ++++++++------- ts/src/lower/pointer.rs | 4 ++-- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/ts/fixtures/lang/expected/elements.Storage.ts b/ts/fixtures/lang/expected/elements.Storage.ts index 17dc286e2..3604fa536 100644 --- a/ts/fixtures/lang/expected/elements.Storage.ts +++ b/ts/fixtures/lang/expected/elements.Storage.ts @@ -31,11 +31,11 @@ export function write(self: Storage, offset: rt.usize, src: rt.ByteSlice): void } export function read(self: Storage, len: rt.usize): rt.ByteSlice { - return rt.ptrByteSlice(self.data, len, false); + return rt.ptrByteSlice(self.data, len, true); } export function drop$ops_Drop(self: Storage): void { - (({ v: rt.ptrByteSlice(self.data, self.capacity, true) }.v)); + (({ v: rt.ptrByteSlice(self.data, self.capacity, false) }.v)); } export { new_ as new }; diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index de1ca6481..0c010c4db 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -111,7 +111,7 @@ export function segments(data: rt.ByteSlice, out: rt.ByteVec): void { rt.ptrCopy(p2, rt.byteSliceAsPtr(rt.byteSliceFrom(a)), rt.min(v.length, 4)); const byte: rt.Place = rt.ptrAdd(rt.byteSliceAsPtr(rt.byteSliceFrom(a)), 1); rt.ptrAdd(byte, 2).v = 85; - rt.byteVecExtendFromSlice(out, rt.ptrByteSlice(rt.ptrAdd(byte, -1), 4, false)); + rt.byteVecExtendFromSlice(out, rt.ptrByteSlice(rt.ptrAdd(byte, -1), 4, true)); const one: rt.Place = rt.sliceAsPtr(a, 2); one.v = one.v ^ 15; rt.byteVecPush(out, one.v); @@ -223,7 +223,12 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { _68[0] = rt.u32Add(_68[0], 1); rt.byteVecPush(out, rt.ptrAdd(rt.ptrAdd(at, 1), -1).v & 0xff); rt.byteVecPush(out, rt.ptrOffsetFrom(rt.ptrAdd(at, 1), at) & 0xff); + rt.byteVecPush(out, rt.ptrOffsetFrom(rt.sliceAsPtr(rt.ptrSlice(at, 0)), at) & 0xff); rt.byteVecPush(out, lone & 0xff); + let byte: rt.u8 = n ^ 90; + const _97: rt.ByteSlice = rt.ptrByteSlice(rt.place(() => byte, (value2) => byte = value2), 1, true); + rt.idx(_97, 0); + rt.byteVecPush(out, _97.data[_97.start]); const a: rt.u32[] = [n as rt.u32, 1, 2, 3]; const base: rt.Place = rt.sliceAsPtr(a); const part: rt.u32[] = rt.sliceRangeMut(a, 1, 3); @@ -232,7 +237,7 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecPush(out, rt.ptrOffsetFrom(at2, base) & 0xff); at2.v = rt.u32Add(at2.v, 5); rt.byteVecPush(out, rt.ptrOffsetFrom(rt.sliceAsPtr(rt.ptrSlice(rt.ptrAdd(at2, 1), 0)), base) & 0xff); - rt.byteVecPush(out, rt.ptrOffsetFrom(rt.byteSliceAsPtr(rt.ptrByteSlice(rt.ptrAdd(rt.byteSliceAsPtr(rt.byteSliceFrom(bytes)), 2), 0, false)), p) & 0xff); + rt.byteVecPush(out, rt.ptrOffsetFrom(rt.byteSliceAsPtr(rt.ptrByteSlice(rt.ptrAdd(rt.byteSliceAsPtr(rt.byteSliceFrom(bytes)), 2), 0, true)), p) & 0xff); rt.iterExtendBytes(out, rt.iterMap(rt.iterSlice(a), (_2: rt.u32): rt.u8 => { return _2 & 0xff; }), undefined, true); diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 04db37c83..4e547e449 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -281,8 +281,11 @@ pub fn edges(data: &[u8], out: &mut Vec) { slice::from_raw_parts_mut(at, 1)[0] += 1; out.push(at.add(1).sub(1).read() as u8); out.push(at.add(1).offset_from(at) as u8); + out.push(slice::from_raw_parts(at, 0).as_ptr().offset_from(at) as u8); } out.push(lone as u8); + let byte = n ^ 0x5a; + out.push(unsafe { slice::from_raw_parts(&raw const byte, 1)[0] }); let mut a = [u32::from(n), 1, 2, 3]; let base = a.as_mut_ptr(); let part = &mut a[1..3]; diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 46b333d6a..347438a9f 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -260,23 +260,24 @@ export function ptrCopy(src: Place, dst: Place, n: number, copy?: (valu /** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` elements from `p`. */ export function ptrSlice(p: Place, len: number): T[] { // One of none from a dangling pointer points nowhere. - if (len === 0 && !(p instanceof AtIndex)) return []; + if (len === 0 && (p === NULL || p === DANGLING)) return []; const at = elemOf(p); const array = at.array as T[]; return at.index === 0 && len === array.length ? array : sliceView(array, at.index, at.index + len); } /** - * `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` bytes from `p`, which - * may be `mutable` only if they are in an array of bytes. + * `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` bytes from `p`. Only a + * `shared` reference to bytes in no array of bytes may be a copy of them, since nothing + * writes them while it lives. */ -export function ptrByteSlice(p: Place, len: number, mutable: boolean): ByteSlice { - if (len === 0 && !(p instanceof AtIndex)) return new ByteSlice(new Uint8Array(0), 0, 0); +export function ptrByteSlice(p: Place, len: number, shared: boolean): ByteSlice { + if (len === 0 && (p === NULL || p === DANGLING)) return new ByteSlice(new Uint8Array(0), 0, 0); const at = elemOf(p); if (at.array instanceof Uint8Array) return new ByteSlice(at.array, at.index, len); // A byte in no array, which no `Uint8Array` can share. - if (mutable) throw new Error("rust2-ts: a mutable slice of a byte in no array of bytes"); - return new ByteSlice(Uint8Array.of(p.v), 0, len); + if (!shared) throw new Error("rust2-ts: a raw or mutable slice of a byte in no array of bytes"); + return new ByteSlice(Uint8Array.from({ length: len }, () => p.v), 0, len); } /** diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index c71381990..93b198fcc 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -708,8 +708,8 @@ pub fn call<'tcx>( } Some(why) => return Ok(Some(b.err(span, why))), None if matches!(pointee().kind(), ty::Uint(ty::UintTy::U8)) => { - let mutable = !matches!(name, "from_raw_parts" | "slice_from_raw_parts"); - Expr::rt("ptrByteSlice", vec![p, len, Expr::lit(mutable.to_string())]) + let shared = name == "from_raw_parts"; + Expr::rt("ptrByteSlice", vec![p, len, Expr::lit(shared.to_string())]) } None => Expr::rt("ptrSlice", vec![p, len]), }; From 505dcdee5c342c117fb3bb89b6c4ac21ed760eb2 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 13:36:05 -0800 Subject: [PATCH 07/34] chore(ts): refresh the lang harness snapshot after merging main Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/harness.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index b73620da1..be5659eba 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -23,6 +23,7 @@ import { defaults as defaults_defaults } from "./defaults.ts"; import { durations } from "./durations.ts"; import { dyns, shared as dyns_shared, anys, fns } from "./dyns.ts"; import { edits as edits_edits, bytes as edits_bytes } from "./edits.ts"; +import { vecs as elements_vecs, arrays as elements_arrays, segments as elements_segments, inline, edges as elements_edges } from "./elements.ts"; import { eqs } from "./errors.ts"; import { fields as fields_fields } from "./fields.ts"; import { floats, mulAdds as floats_mulAdds, formats as floats_formats, parses as floats_parses } from "./floats.ts"; @@ -44,7 +45,7 @@ import { u8s as methods_u8s, u16s as methods_u16s, u32s as methods_u32s, u64s as import { nested as nested_nested } from "./nested.ts"; import { objects } from "./objects.ts"; import { orders, bytes as orders_bytes, tuples as orders_tuples, held as orders_held, btrees } from "./orders.ts"; -import { lists as pointers_lists, shared as pointers_shared, places as pointers_places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound as pointers_rebound, lapsed, writers, made as pointers_made, segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken as pointers_taken, bytes as pointers_bytes, loaded, reattached, partWritten, aimed } from "./pointers.ts"; +import { lists as pointers_lists, shared as pointers_shared, places as pointers_places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound as pointers_rebound, lapsed, writers, made as pointers_made, segments as pointers_segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken as pointers_taken, bytes as pointers_bytes, loaded, reattached, partWritten, aimed } from "./pointers.ts"; import { refs, toRefs } from "./refs.ts"; import { results as results_results } from "./results.ts"; import { returns } from "./returns.ts"; @@ -224,7 +225,7 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::slices", cows_slices as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::bytes", cows_bytes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u8s", wraps_u8s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u16s", wraps_u16s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u32s", wraps_u32s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u64s", wraps_u64s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u128s", wraps_u128s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::usizes", wraps_usizes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i8s", wraps_i8s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i16s", wraps_i16s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i32s", wraps_i32s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i64s", wraps_i64s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i128s", wraps_i128s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::isizes", wraps_isizes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::enums", (_2174: rt.u8, data74: rt.ByteSlice, out75: rt.ByteVec): undefined => { enums(data74, out75); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::segments", (_2175: rt.u8, data75: rt.ByteSlice, out76: rt.ByteVec): undefined => { - segments(data75, out76); + pointers_segments(data75, out76); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::uninit_runs", (_2176: rt.u8, data76: rt.ByteSlice, out77: rt.ByteVec): undefined => { uninitRuns(data76, out77); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::beside_written", (_2177: rt.u8, data77: rt.ByteSlice, out78: rt.ByteVec): undefined => { @@ -273,7 +274,17 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) capacity_deques(a0153, a1153.data, a1153.start, a1153.length, a2153); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::heaps", (a0154: rt.u8, a1154: rt.ByteSlice, a2154: rt.ByteVec): undefined => { capacity_heaps(a0154, a1154.data, a1154.start, a1154.length, a2154); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::maps", capacity_maps as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::maps", capacity_maps as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::vecs", (_2196: rt.u8, data96: rt.ByteSlice, out97: rt.ByteVec): undefined => { + elements_vecs(data96, out97); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::arrays", (_2197: rt.u8, data97: rt.ByteSlice, out98: rt.ByteVec): undefined => { + elements_arrays(data97, out98); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::segments", (_2198: rt.u8, data98: rt.ByteSlice, out99: rt.ByteVec): undefined => { + elements_segments(data98, out99); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::inline", (_2199: rt.u8, data99: rt.ByteSlice, out100: rt.ByteVec): undefined => { + inline(data99, out100); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::edges", (_2200: rt.u8, data100: rt.ByteSlice, out101: rt.ByteVec): undefined => { + elements_edges(data100, out101); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } /** Run one case. */ From 762bbd97b3f5681d0843942e790f1176f436523d Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 14:25:29 -0800 Subject: [PATCH 08/34] feat(ts): refuse pointers a body shows crossing what JS keeps apart Per the maintainer's decision on #400: offset_from between pointers into a value in no array or a copied shared subslice and another array, a raw or mutable slice of a lone byte, and constant offsets, slices, and copies reaching past either are refused with a span. Pointers only run time shows still fail loudly; ptrSlice, ptrByteSlice, and ptrCopy now check their whole extent. Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-unsafe.md | 3 + ts/README.md | 2 +- ts/fixtures/lang/src/elements.rs | 11 +- ts/fixtures/reject/census.md | 56 +++++- ts/fixtures/reject/expected.txt | 10 ++ ts/fixtures/reject/src/lib.rs | 43 +++++ ts/fixtures/reject/ts.toml | 3 + ts/runtime/rt.ts | 8 +- ts/runtime/rt/collections.ts | 15 +- ts/src/lower/mir/call.rs | 3 + ts/src/lower/mir/mod.rs | 1 + ts/src/lower/mir/origin.rs | 300 +++++++++++++++++++++++++++++++ ts/tests/pointers.ts | 52 ++++++ ts/tests/runtime.rs | 14 ++ 14 files changed, 508 insertions(+), 13 deletions(-) create mode 100644 ts/src/lower/mir/origin.rs create mode 100644 ts/tests/pointers.ts diff --git a/quest/m3/ts-unsafe.md b/quest/m3/ts-unsafe.md index b4722439c..722d4fa56 100644 --- a/quest/m3/ts-unsafe.md +++ b/quest/m3/ts-unsafe.md @@ -28,6 +28,9 @@ none of its own `unsafe`. silently differs from native where Rust's behavior is defined. - Element pointers into arrays of values JS copies are `rt.AtIndex`, `{ array, index }`, which `add`, `offset`, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` act on. + Decided with the maintainer on 2026-11-01: where one body shows a pointer crossing what + JS keeps apart (a value in no array, a copied shared subslice), translation refuses it + with a span; a pointer from elsewhere, such as a field, fails there loudly at run time. A pointer to an element JS keeps as an object, such as a tuple or the crate's struct, is that object, which knows no array it is in, so arithmetic on one stays refused: hashbrown's buckets and kio's `log.rs` reach it. smallvec's pointer arithmetic diff --git a/ts/README.md b/ts/README.md index add59aba3..0e516f348 100644 --- a/ts/README.md +++ b/ts/README.md @@ -101,7 +101,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | tuple, `[T; N]`, `&[T]`, `Box<[T]>`, `Vec` | an array; for bytes, `Uint8Array` for `[u8; N]`, `rt.ByteSlice` for `&[u8]`, `&[u8; N]`, and `Box<[u8]>`, and `rt.ByteVec` for `Vec` | | `Cow<[T]>` | `rt.Cow`: the `Vec` it owns, or an `rt.Borrowed` holding the slice it borrows; `rt.ByteCow` for bytes, an `rt.ByteVec` or an `rt.ByteSlice` | | `&T`, `&mut T`, `Box`, `Reverse`, `NonZero` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, a `String`, or a reference, or to an `Arc` or a `Waker`, is an `rt.Place`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rt.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view; one to a value in no array is element 0 of an array of one, as in Rust, and a read or write through one past the array JS keeps it in fails at run time, as past a shared subslice, which JS copies; a pointer into an array of values JS keeps as objects is refused; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | +| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rt.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, or copy the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in, fails at run time; a pointer into an array of values JS keeps as objects is refused; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | | `MaybeUninit` | `T`: `undefined` until written, or an object whose parts kept as objects are there, holding `undefined` | | `union` | an object of slots named by offset, `at0` and `at8`, each holding the field part kept there; fields must overlap in the same type, or in a non-null pointer and an `Option` of it | | an iterator | `rt.Iter`, whose adaptors are runtime functions: `rt.iterMap(rt.iterSlice(v), f)` | diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 4e547e449..e1ed6fbac 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -254,7 +254,7 @@ pub fn inline(data: &[u8], out: &mut Vec) { /// Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as /// the array of one Rust treats it as, and into a mutable part of an array, measured from -/// the array, and the pointer an empty slice keeps. +/// the array, the pointer an empty slice keeps, and within a shared part, which JS copies. pub fn edges(data: &[u8], out: &mut Vec) { for &n in data { let bytes = [n, 1, 2, 3]; @@ -299,6 +299,15 @@ pub fn edges(data: &[u8], out: &mut Vec) { out.push(none.as_ptr().offset_from(p) as u8); } out.extend(a.iter().map(|&x| x as u8)); + // Within a shared subslice, a copy, and to its end. + let lanes = [u32::from(n), 4, 5, 6]; + let shared = &lanes[1..3]; + let first = shared.as_ptr(); + unsafe { + out.push(*first.add(1) as u8); + out.push(first.add(2).offset_from(first) as u8); + out.push(slice::from_raw_parts(first.add(1), 1)[0] as u8); + } } } diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 2e232f79c..0b99d2ba6 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,7 +1,7 @@ # Census of `reject-fixture` -- Refusals: 226 -- Function instances: 274 +- Refusals: 236 +- Function instances: 277 - Generic functions no instance reached, lowered over their type parameters: 13 ## By construct @@ -27,8 +27,11 @@ | 2 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | fixtures/reject/src/lib.rs:21:4 | | 2 | `_`, a trait object of another crate's trait, which is not supported yet | fixtures/reject/src/lib.rs:11:16 | | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | fixtures/reject/src/lib.rs:459:23 | +| 2 | `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/lib.rs:2205:5 | | 2 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:550:27 | | 2 | a coercion to `_`, which is not supported yet | fixtures/reject/src/lib.rs:450:40 | +| 2 | a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2225:18 | +| 2 | a raw or mutable slice of a byte in no array of bytes | fixtures/reject/src/lib.rs:2211:12 | | 2 | a raw pointer to a place of `()`, which has no address of its own | fixtures/reject/src/lib.rs:723:15 | | 2 | an iterator of the crate implementing `_`, which is not supported yet | fixtures/reject/src/lib.rs:145:1 | | 2 | comparing pointers to `_`, each a place JS makes of its own | fixtures/reject/src/lib.rs:591:2 | @@ -50,6 +53,7 @@ | 1 | `clone_from` of `_`, whose items' `clone_from` is their own, which is not supported yet | fixtures/reject/src/lib.rs:2113:4 | | 1 | `collect` of `_`, which sorts by `_`'s own `Ord`, of entries that drop, which is not supported yet | fixtures/reject/src/lib.rs:950:22 | | 1 | `fmt::Arguments` with a placeholder, or not made in place, written to `_`'s own `fmt::Write`, which is not supported yet: std splits what a placeholder writes into `write_str` calls the runtime does not make | fixtures/reject/src/lib.rs:55:10 | +| 1 | `offset_from` between a pointer into a shared subslice of values JS copies and one into an array of this body's own, which JS keeps in different arrays | fixtures/reject/src/lib.rs:2205:88 | | 1 | `offset_from` of pointers to `()`, which has no bytes | fixtures/reject/src/lib.rs:2167:13 | | 1 | `offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2160:36 | | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | fixtures/reject/src/lib.rs:209:40 | @@ -58,6 +62,7 @@ | 1 | a coercion of `_` to `_`, a pointer to a slice | fixtures/reject/src/lib.rs:1709:28 | | 1 | a coercion to `_` | fixtures/reject/src/lib.rs:291:2 | | 1 | a constant of type `_`, a pointer into memory translation can't name | fixtures/reject/src/lib.rs:773:3 | +| 1 | a copy reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2223:3 | | 1 | a dangling pointer to `_`, a value of no bytes made of more than structs and tuples | fixtures/reject/src/lib.rs:732:11 | | 1 | a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is | fixtures/reject/src/lib.rs:77:34 | | 1 | a mapped guard of `_`, which JS can't write in place | fixtures/reject/src/lib.rs:2147:10 | @@ -65,6 +70,7 @@ | 1 | a mutable static, which any code may write | fixtures/reject/src/lib.rs:61:11 | | 1 | a pointer into `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2154:12 | | 1 | a pointer into an array of `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2159:30 | +| 1 | a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2227:27 | | 1 | a pointer's address, which JS has no counterpart of | fixtures/reject/src/lib.rs:555:17 | | 1 | a printable type must implement `Display` or `Debug` | fixtures/reject/src/lib.rs:64:1 | | 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | fixtures/reject/src/lib.rs:572:10 | @@ -72,6 +78,7 @@ | 1 | a scalar constant of type `_` | fixtures/reject/src/lib.rs:8:2 | | 1 | a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2158:11 | | 1 | a slice of `()`, which has no bytes, from a pointer | fixtures/reject/src/lib.rs:2167:39 | +| 1 | a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2226:6 | | 1 | a trait object of `_`, which is not supported yet | fixtures/reject/src/lib.rs:476:26 | | 1 | an upcast from `_` to `_`, which is not supported yet | fixtures/reject/src/lib.rs:527:32 | | 1 | arithmetic on `_` | fixtures/reject/src/lib.rs:8:2 | @@ -687,6 +694,13 @@ The type or module each refused type or path belongs to.
+
`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 (2) + +- fixtures/reject/src/lib.rs:2205:5: `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/lib.rs:2205:42: `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 + +
+
a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another (2) - fixtures/reject/src/lib.rs:550:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another @@ -701,6 +715,20 @@ The type or module each refused type or path belongs to.
+
a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust (2) + +- fixtures/reject/src/lib.rs:2225:18: a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust +- fixtures/reject/src/lib.rs:2228:21: a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust + +
+ +
a raw or mutable slice of a byte in no array of bytes (2) + +- fixtures/reject/src/lib.rs:2211:12: a raw or mutable slice of a byte in no array of bytes +- fixtures/reject/src/lib.rs:2212:19: a raw or mutable slice of a byte in no array of bytes + +
+
a raw pointer to a place of `()`, which has no address of its own (2) - fixtures/reject/src/lib.rs:723:15: a raw pointer to a place of `()`, which has no address of its own @@ -833,6 +861,12 @@ The type or module each refused type or path belongs to.
+
`offset_from` between a pointer into a shared subslice of values JS copies and one into an array of this body's own, which JS keeps in different arrays (1) + +- fixtures/reject/src/lib.rs:2205:88: `offset_from` between a pointer into a shared subslice of values JS copies and one into an array of this body's own, which JS keeps in different arrays + +
+
`offset_from` of pointers to `()`, which has no bytes (1) - fixtures/reject/src/lib.rs:2167:13: `offset_from` of pointers to `()`, which has no bytes @@ -881,6 +915,12 @@ The type or module each refused type or path belongs to.
+
a copy reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust (1) + +- fixtures/reject/src/lib.rs:2223:3: a copy reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust + +
+
a dangling pointer to `_`, a value of no bytes made of more than structs and tuples (1) - fixtures/reject/src/lib.rs:732:11: a dangling pointer to `Only`, a value of no bytes made of more than structs and tuples @@ -923,6 +963,12 @@ The type or module each refused type or path belongs to.
+
a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust (1) + +- fixtures/reject/src/lib.rs:2227:27: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust + +
+
a pointer's address, which JS has no counterpart of (1) - fixtures/reject/src/lib.rs:555:17: a pointer's address, which JS has no counterpart of @@ -965,6 +1011,12 @@ The type or module each refused type or path belongs to.
+
a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust (1) + +- fixtures/reject/src/lib.rs:2226:6: a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust + +
+
a trait object of `_`, which is not supported yet (1) - fixtures/reject/src/lib.rs:476:26: a trait object of `[u8; 4]`, which is not supported yet diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 2368bbf7e..eef0882b7 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -207,6 +207,16 @@ a slice of `()`, which has no bytes, from a pointer at fixtures/reject/src/lib.r a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2172:13 a raw pointer to `[u32; 3]` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2183:10 a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2185:10 +`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 at fixtures/reject/src/lib.rs:2205:5 +`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 at fixtures/reject/src/lib.rs:2205:42 +`offset_from` between a pointer into a shared subslice of values JS copies and one into an array of this body's own, which JS keeps in different arrays at fixtures/reject/src/lib.rs:2205:88 +a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2211:12 +a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2212:19 +a copy reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2223:3 +a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2225:18 +a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2226:6 +a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2227:27 +a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2228:21 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:125:2 an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet at fixtures/reject/src/lib.rs:145:1 overriding `Iterator::size_hint`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:173:2 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index c64775cca..6e0b3d1a2 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -2186,3 +2186,46 @@ pub fn elements_replaced(n: u32, l: &mut Lanes) -> u32 { l.lanes = [1; 3]; unsafe { *p + *q + a[0] } } + +#[repr(C)] +pub struct Halves { + pub low: u32, + pub high: u32, +} + +/// `offset_from` of pointers the body shows JS keeps in different arrays: two made apart +/// of one variable, two of a struct's fields, and one into a shared subslice, a copy, +/// against one into what it was taken of. +pub fn pointers_apart(n: u32, h: Halves) -> isize { + let a = &raw const n; + let b = &raw const n; + let lanes = [n; 4]; + let part = &lanes[1..3]; + unsafe { + a.offset_from(b) + (&raw const h.high).offset_from(&raw const h.low) + part.as_ptr().offset_from(lanes.as_ptr()) + } +} + +/// 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] } +} + +/// 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 { + let part = &lanes[1..3]; + let mut to = [0u32; 3]; + unsafe { + std::ptr::copy_nonoverlapping(part.as_ptr(), to.as_mut_ptr(), 3); + let tail = lanes.get_unchecked(2..); + *part.as_ptr().sub(1) + + std::slice::from_raw_parts(&raw const h.low, 2)[1] + + *(&raw const h.high).sub(1) + + *tail.as_ptr().offset(-2) + + to[0] + } +} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index cc35da3b7..74149ef78 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -169,5 +169,8 @@ roots = [ "unit_elements", "element_to_array", "elements_replaced", + "pointers_apart", + "lone_byte_slices", + "reached_out", ] printable = ["Meters"] diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 347438a9f..03dc173fc 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -248,13 +248,15 @@ export function ptrOffsetFrom(p: Place, origin: Place): number { export function ptrCopy(src: Place, dst: Place, n: number, copy?: (value: T) => T): void { if (n === 0) return; const [from, to] = [elemOf(src), elemOf(dst)]; + from.reach(n); + to.reach(n); if (ArrayBuffer.isView(from.array) && ArrayBuffer.isView(to.array)) { const bytes = from.array as unknown as Uint8Array; (to.array as unknown as Uint8Array).set(bytes.subarray(from.index, from.index + n), to.index); return; } const values = Array.from({ length: n }, (_, i) => from.array[from.index + i]); - for (let i = 0; i < n; i++) new AtIndex(to.array, to.index + i).v = copy ? copy(values[i]) : values[i]; + for (let i = 0; i < n; i++) to.array[to.index + i] = copy ? copy(values[i]) : values[i]; } /** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` elements from `p`. */ @@ -262,6 +264,7 @@ export function ptrSlice(p: Place, len: number): T[] { // One of none from a dangling pointer points nowhere. if (len === 0 && (p === NULL || p === DANGLING)) return []; const at = elemOf(p); + at.reach(len); const array = at.array as T[]; return at.index === 0 && len === array.length ? array : sliceView(array, at.index, at.index + len); } @@ -274,10 +277,11 @@ export function ptrSlice(p: Place, len: number): T[] { export function ptrByteSlice(p: Place, len: number, shared: boolean): ByteSlice { if (len === 0 && (p === NULL || p === DANGLING)) return new ByteSlice(new Uint8Array(0), 0, 0); const at = elemOf(p); + at.reach(len); if (at.array instanceof Uint8Array) return new ByteSlice(at.array, at.index, len); // A byte in no array, which no `Uint8Array` can share. if (!shared) throw new Error("rust2-ts: a raw or mutable slice of a byte in no array of bytes"); - return new ByteSlice(Uint8Array.from({ length: len }, () => p.v), 0, len); + return new ByteSlice(Uint8Array.from({ length: len }, (_, i) => at.array[at.index + i]), 0, len); } /** diff --git a/ts/runtime/rt/collections.ts b/ts/runtime/rt/collections.ts index e35127623..7137324c4 100644 --- a/ts/runtime/rt/collections.ts +++ b/ts/runtime/rt/collections.ts @@ -373,23 +373,24 @@ export class AtIndex implements Place { } get v(): T { - this.check(); + this.reach(1); return this.array[this.index]; } set v(value: T) { - this.check(); + this.reach(1); this.array[this.index] = value; } /** - * Fails past either end of the array, which JS doesn't keep Rust's memory around: a JS - * array grows by a write past its end, where a `Vec`'s length stays until `set_len`, and - * a shared subslice is a copy, which a pointer moved past it would read nothing of. + * Fails where `n` elements from here run past either end of the array, which JS + * doesn't keep Rust's memory around: a JS array grows by a write past its end, where a + * `Vec`'s length stays until `set_len`, and a shared subslice is a copy, which a + * pointer moved past it would read nothing of. */ - private check(): void { + reach(n: number): void { const length = (this.array as ArrayLike).length; - if (this.index < 0 || this.index >= length) { + if (this.index < 0 || this.index + n > length) { throw new Error("rust2-ts: a read or write through a pointer past the array JS keeps it in"); } } diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 6c0b7bd04..b1fe7ae3e 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -102,6 +102,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.call_def(def_id, gargs.skip_binder(), args, result, span, &mut pre), true, ), + ty::FnDef(def_id, gargs) if let Some(why) = self.stray(def_id, gargs.skip_binder(), args) => { + (self.err(span, why), true) + } ty::FnDef(def_id, gargs) => match self.intercept(def_id, gargs.skip_binder(), args, dest, span, &mut pre) { Some(Some(done)) => done, // Done: the destination is set. diff --git a/ts/src/lower/mir/mod.rs b/ts/src/lower/mir/mod.rs index 8ac42d553..cd393c311 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 origin; mod place; mod rvalue; mod scalar; diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs new file mode 100644 index 000000000..06d052a47 --- /dev/null +++ b/ts/src/lower/mir/origin.rs @@ -0,0 +1,300 @@ +//! Where a raw pointer a body makes points, as far as the body shows. JS keeps a value in +//! no array, and a shared subslice of values it copies, apart from what holds them in +//! Rust, so a pointer leaving one, or measured against another, fails at run time; where +//! the body proves that it does, translation refuses it with a span instead. A pointer the +//! body takes from elsewhere, such as a field or a parameter, is left to the runtime. + +use rustc_middle::mir::{self, BasicBlock, Local, Location, Operand, ProjectionElem, Rvalue, TerminatorKind}; +use rustc_middle::ty::{self, GenericArgsRef}; +use rustc_span::Spanned; + +use super::Fx; +use crate::lower::library::{self, Std}; +use crate::lower::num::Int; +use crate::lower::pointer::Pointee; + +/// The array JS keeps what a pointer points at in. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Array { + /// An array of one, around the place a `&raw` here took, which each `&raw` makes anew. + Lone(Location), + /// A shared subslice of values JS copies, which is a copy, made by the call ending this + /// block, with its length if the body fixes it. + Copy(BasicBlock, Option), + /// The array of a variable of this body's own, or of a field of one. + Owned, +} + +impl Array { + /// How many elements the array holds, if the body shows it. + fn len(self) -> Option { + match self { + Array::Lone(_) => Some(1), + Array::Copy(_, len) => len, + Array::Owned => None, + } + } + + /// What the array is, as a refusal names it. + fn what(self) -> &'static str { + match self { + Array::Lone(_) => "a value in no array", + Array::Copy(..) => "a shared subslice of values JS copies", + Array::Owned => "an array of this body's own", + } + } +} + +/// Where a pointer points: into which array, at which index if the body fixes it. +#[derive(Clone, Copy)] +struct Origin { + array: Array, + index: Option, +} + +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 subslice. + pub(super) fn stray( + &self, + def_id: rustc_hir::def_id::DefId, + gargs: GenericArgsRef<'tcx>, + args: &[Spanned>], + ) -> Option { + 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 self.b.t.zero_sized(pointee) || self.b.t.pointee(pointee) != Pointee::Place { + return None; + } + let out = |o: Origin, n: Option, what: &str| { + let index = o.index?; + let past = o.array.len().is_some_and(|len| index + n.unwrap_or(0) > len); + (index < 0 || past).then(|| { + format!( + "{what} reaching past {}, which JS keeps apart from what holds it in Rust", + o.array.what() + ) + }) + }; + match (owner.as_str(), name.as_str()) { + ("ptr" | "NonNull", "offset_from" | "offset_from_unsigned") => { + let (a, b) = (self.origin(arg(0)?)?, self.origin(arg(1)?)?); + let apart = match (a.array, b.array) { + (Array::Owned, Array::Owned) => false, + (a, b) => a != b, + }; + apart.then(|| { + format!( + "`offset_from` between a pointer into {} and one into {}, which JS keeps in different arrays", + a.array.what(), + b.array.what() + ) + }) + } + ("ptr" | "NonNull", "add" | "sub" | "offset") => { + let o = self.moved(self.origin(arg(0)?)?, name.as_str(), arg(1)?); + out(o, None, "a pointer") + } + ("raw", "from_raw_parts" | "from_raw_parts_mut") + | ("ptr", "slice_from_raw_parts" | "slice_from_raw_parts_mut") + | ("NonNull", "slice_from_raw_parts") => { + let o = self.origin(arg(0)?)?; + let byte = matches!(pointee.kind(), ty::Uint(ty::UintTy::U8)); + if byte && name != "from_raw_parts" && matches!(o.array, Array::Lone(_)) { + return Some("a raw or mutable slice of a byte in no array of bytes".into()); + } + out(o, Some(self.const_int(arg(1)?)?), "a slice") + } + ( + "ptr", + "copy" + | "copy_nonoverlapping" + | "copy_to" + | "copy_to_nonoverlapping" + | "copy_from" + | "copy_from_nonoverlapping", + ) + | ("NonNull", "copy_to" | "copy_to_nonoverlapping" | "copy_from" | "copy_from_nonoverlapping") => { + let n = self.const_int(arg(2)?); + [arg(0)?, arg(1)?] + .into_iter() + .find_map(|p| out(self.origin(p)?, n, "a copy")) + } + _ => None, + } + } + + /// Where the pointer `op` points, if the body shows it. + fn origin(&self, op: &Operand<'tcx>) -> Option { + let local = self.traced(op)?; + if let Some((bb, i)) = self.def_at[local] { + let rvalue = self.def_rvalue(local)?; + return match rvalue { + Rvalue::Use(op, _) + | Rvalue::Cast( + mir::CastKind::PtrToPtr + | mir::CastKind::PointerCoercion(ty::adjustment::PointerCoercion::MutToConstPointer, _), + op, + _, + ) if self.same_pointee(op, local) => self.origin(op), + Rvalue::RawPtr(mir::RawPtrKind::Const | mir::RawPtrKind::Mut, place) => { + match place.projection.as_slice() { + // `&raw mut *p` is `p`. + [ProjectionElem::Deref] => self.origin(&Operand::Copy(mir::Place::from(place.local))), + [.., ProjectionElem::Deref] => None, + [.., ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. }] => { + (!place.projection.contains(&ProjectionElem::Deref)).then_some(Origin { + array: Array::Owned, + index: None, + }) + } + _ => Some(Origin { + array: Array::Lone(Location { + block: bb, + statement_index: i, + }), + index: Some(0), + }), + } + } + _ => None, + }; + } + let (def_id, _, args) = self.def_call(local)?; + let (owner, name) = library::key(self.tcx, def_id)?; + match (owner.as_str(), name.as_str()) { + ("ptr" | "NonNull", "add" | "sub" | "offset") => { + Some(self.moved(self.origin(&args[0].node)?, name.as_str(), &args[1].node)) + } + ("NonNull", "as_ptr" | "new_unchecked") => self.origin(&args[0].node), + ("slice" | "Vec", "as_ptr" | "as_mut_ptr") => Some(Origin { + array: self.array(&args[0].node)?, + index: Some(0), + }), + _ => None, + } + } + + /// `o` moved by the offset `op` as `add`, `sub`, or `offset` moves it. + fn moved(&self, o: Origin, name: &str, op: &Operand<'tcx>) -> Origin { + let n = self.const_int(op).map(|n| match name { + "sub" => -n, + _ => n, + }); + Origin { + index: o.index.zip(n).map(|(i, n)| i + n), + ..o + } + } + + /// The array the slice or `Vec`, or reference to one, `op` is. + fn array(&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::PointerCoercion(..), op, _) => self.array(op), + Rvalue::Ref(_, _, place) | Rvalue::CopyForDeref(place) => match place.projection.as_slice() { + [ProjectionElem::Deref] => self.array(&Operand::Copy(mir::Place::from(place.local))), + projection + if !projection.contains(&ProjectionElem::Deref) + && match place.ty(&self.mir.local_decls, self.tcx).ty.kind() { + ty::Array(..) => true, + ty::Adt(adt, _) => Std::of(self.tcx, adt.did()) == Some(Std::Vec), + _ => false, + } => + { + Some(Array::Owned) + } + _ => None, + }, + _ => None, + }; + } + let (def_id, gargs, args) = self.def_call(local)?; + let (owner, name) = library::key(self.tcx, def_id)?; + let container = gargs.type_at(0).peel_refs(); + // `get_unchecked` is generic over the element, not the slice. + let elem = match (owner.as_str(), name.as_str(), container.kind()) { + ("slice", "get_unchecked", _) => container, + ("Index", "index", ty::Slice(e) | ty::Array(e, _)) => *e, + ("Index", "index", ty::Adt(adt, a)) if Std::of(self.tcx, adt.did()) == Some(Std::Vec) => a.type_at(0), + _ => return None, + }; + let range = gargs.type_at(1); + // A shared range of bytes is a view of them. + if matches!(elem.kind(), ty::Uint(ty::UintTy::U8)) || Int::of(range).is_some() { + return None; + } + let call = self.def_block(local)?; + Some(Array::Copy(call, self.range_len(&args[1].node, container))) + } + + /// How many elements the range `op` takes of `container`, if the body fixes it. + fn range_len(&self, op: &Operand<'tcx>, container: ty::Ty<'tcx>) -> Option { + let local = self.traced(op)?; + let Rvalue::Aggregate(kind, ops) = self.def_rvalue(local)? else { + return None; + }; + let mir::AggregateKind::Adt(did, ..) = **kind else { + return None; + }; + let all = match container.kind() { + ty::Array(_, n) => n.try_to_target_usize(self.tcx).map(i128::from), + _ => None, + }; + let ops: Vec<_> = ops.iter().map(|op| self.const_int(op)).collect(); + match (self.tcx.item_name(did).as_str(), ops.as_slice()) { + ("Range", [Some(start), Some(end)]) => Some(end - start), + ("RangeTo", [Some(end)]) => Some(*end), + ("RangeFrom", [Some(start)]) => Some(all? - start), + ("RangeFull", []) => all, + _ => None, + } + } + + /// The local `op` reads whole, if the body assigns it once. + fn traced(&self, op: &Operand<'tcx>) -> Option { + let local = op.place()?.as_local()?; + (self.defs[local] == 1 && !self.is_arg(local)).then_some(local) + } + + /// Whether `op` is a pointer to what `local` points at. + fn same_pointee(&self, op: &Operand<'tcx>, local: Local) -> bool { + let pointee = |ty: ty::Ty<'tcx>| ty.builtin_deref(true); + let from = op.ty(&self.mir.local_decls, self.tcx); + pointee(from).is_some() && pointee(from) == pointee(self.mir.local_decls[local].ty) + } + + /// The function, its generic arguments, and its arguments, of the call that assigns `local`. + fn def_call( + &self, + local: Local, + ) -> Option<( + rustc_hir::def_id::DefId, + GenericArgsRef<'tcx>, + &[Spanned>], + )> { + let bb = self.def_block(local)?; + let TerminatorKind::Call { func, args, .. } = &self.mir.basic_blocks[bb].terminator().kind else { + return None; + }; + let ty::FnDef(def_id, gargs) = *func.ty(&self.mir.local_decls, self.tcx).kind() else { + return None; + }; + Some((def_id, gargs.skip_binder(), args)) + } + + /// The block whose call assigns `local`. + fn def_block(&self, local: Local) -> Option { + self.mir + .basic_blocks + .iter_enumerated() + .find_map(|(bb, data)| match &data.terminator().kind { + TerminatorKind::Call { destination, .. } if destination.as_local() == Some(local) => Some(bb), + _ => None, + }) + } +} diff --git a/ts/tests/pointers.ts b/ts/tests/pointers.ts new file mode 100644 index 000000000..1cf31b621 --- /dev/null +++ b/ts/tests/pointers.ts @@ -0,0 +1,52 @@ +// A pointer the translation can't follow, such as one a struct's field holds, fails with +// a rust2-ts error where JS keeps apart what Rust's reaches, which native Rust can't show: +// there each is defined, as `repr(C)` fields and a shared subslice's parent are one +// allocation, and a `Vec`'s capacity is past its length. +// +// usage: node pointers.ts + +import { strict as assert } from "node:assert"; + +import * as rt from "../runtime/rt.ts"; + +const unsure = (f: () => void) => assert.throws(f, (e: Error) => !(e instanceof rt.Panic) && e.message.startsWith("rust2-ts: ")); + +// Two `&raw const x` of one variable, each its own `rt.Place`. +let x = 5; +const a = rt.place(() => x, (v) => (x = v)); +const b = rt.place(() => x, (v) => (x = v)); +unsure(() => rt.ptrOffsetFrom(a, b)); +assert.equal(rt.ptrOffsetFrom(rt.ptrAdd(a, 1), a), 1); + +// A `repr(C)` struct's fields, each a value in no array: reached from the other, and as a slice of both. +const pair = { low: 1, high: 2 }; +const low = rt.place(() => pair.low, (v) => (pair.low = v)); +const high = rt.place(() => pair.high, (v) => (pair.high = v)); +unsure(() => rt.ptrOffsetFrom(high, low)); +unsure(() => rt.ptrAdd(high, -1).v); +unsure(() => rt.ptrSlice(low, 2)); +unsure(() => rt.ptrCopy(low, high, 2)); + +// A shared subslice, which is a copy: before it, past it, and against its parent. +const lanes = [1, 2, 3, 4]; +const part = rt.sliceRange(lanes, 1, 3); +unsure(() => rt.ptrAdd(rt.sliceAsPtr(part), -1).v); +unsure(() => rt.ptrAdd(rt.sliceAsPtr(part), 2).v); +unsure(() => rt.ptrOffsetFrom(rt.sliceAsPtr(part), rt.sliceAsPtr(lanes))); +unsure(() => rt.ptrSlice(rt.sliceAsPtr(part), 3)); +unsure(() => rt.ptrCopy(rt.sliceAsPtr(part), rt.sliceAsPtr(lanes), 3)); +assert.equal(rt.ptrAdd(rt.sliceAsPtr(part), 1).v, 3); + +// A byte in no array, as a raw or mutable slice, and as a shared one past it. +let byte = 3; +const lone = rt.place(() => byte, (v) => (byte = v)); +unsure(() => rt.ptrByteSlice(lone, 1, false)); +unsure(() => rt.ptrByteSlice(lone, 2, true)); +assert.deepEqual(rt.ptrByteSlice(lone, 1, true).data, Uint8Array.of(3)); + +// A write into a `Vec`'s capacity past its length, which a JS array would grow by. +const vec: number[] = [7]; +unsure(() => { + rt.ptrAdd(rt.sliceAsPtr(vec), 1).v = 8; +}); +assert.equal(vec.length, 1); diff --git a/ts/tests/runtime.rs b/ts/tests/runtime.rs index 07e642470..506da90ee 100644 --- a/ts/tests/runtime.rs +++ b/ts/tests/runtime.rs @@ -294,3 +294,17 @@ fn unicode() { path.display() ); } + +/// A pointer the translation can't follow fails where JS keeps apart what Rust's reaches, +/// which `pointers.ts` checks. +#[test] +fn pointers_apart() { + let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("pointers.ts"); + let output = Command::new("node") + .arg(script) + .output() + .expect("running node, which the dev shell provides"); + assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr)); +} From 7b65f42bb204572f6e502278465c40a450da2781 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 14:35:55 -0800 Subject: [PATCH 09/34] fix(ts): trace slices from raw parts, and type arguments past lifetimes A shared slice of a byte in no array is a copy, so its pointer measured against the byte's is refused; a slice from raw parts otherwise starts where its pointer points. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.md | 49 +++++++++++++++++-------------- ts/fixtures/reject/expected.txt | 21 +++++++------- ts/fixtures/reject/src/lib.rs | 14 ++++++--- ts/src/lower/mir/origin.rs | 51 +++++++++++++++++++++------------ ts/tests/pointers.ts | 7 +++-- 5 files changed, 87 insertions(+), 55 deletions(-) diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 0b99d2ba6..27b20e5c1 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,6 +1,6 @@ # Census of `reject-fixture` -- Refusals: 236 +- Refusals: 237 - Function instances: 277 - Generic functions no instance reached, lowered over their type parameters: 13 @@ -27,11 +27,11 @@ | 2 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | fixtures/reject/src/lib.rs:21:4 | | 2 | `_`, a trait object of another crate's trait, which is not supported yet | fixtures/reject/src/lib.rs:11:16 | | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | fixtures/reject/src/lib.rs:459:23 | -| 2 | `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/lib.rs:2205:5 | +| 2 | `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/lib.rs:2208:5 | | 2 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:550:27 | | 2 | a coercion to `_`, which is not supported yet | fixtures/reject/src/lib.rs:450:40 | -| 2 | a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2225:18 | -| 2 | a raw or mutable slice of a byte in no array of bytes | fixtures/reject/src/lib.rs:2211:12 | +| 2 | a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2231:18 | +| 2 | a raw or mutable slice of a byte in no array of bytes | fixtures/reject/src/lib.rs:2217:12 | | 2 | a raw pointer to a place of `()`, which has no address of its own | fixtures/reject/src/lib.rs:723:15 | | 2 | an iterator of the crate implementing `_`, which is not supported yet | fixtures/reject/src/lib.rs:145:1 | | 2 | comparing pointers to `_`, each a place JS makes of its own | fixtures/reject/src/lib.rs:591:2 | @@ -53,7 +53,8 @@ | 1 | `clone_from` of `_`, whose items' `clone_from` is their own, which is not supported yet | fixtures/reject/src/lib.rs:2113:4 | | 1 | `collect` of `_`, which sorts by `_`'s own `Ord`, of entries that drop, which is not supported yet | fixtures/reject/src/lib.rs:950:22 | | 1 | `fmt::Arguments` with a placeholder, or not made in place, written to `_`'s own `fmt::Write`, which is not supported yet: std splits what a placeholder writes into `write_str` calls the runtime does not make | fixtures/reject/src/lib.rs:55:10 | -| 1 | `offset_from` between a pointer into a shared subslice of values JS copies and one into an array of this body's own, which JS keeps in different arrays | fixtures/reject/src/lib.rs:2205:88 | +| 1 | `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/lib.rs:2211:21 | +| 1 | `offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays | fixtures/reject/src/lib.rs:2210:20 | | 1 | `offset_from` of pointers to `()`, which has no bytes | fixtures/reject/src/lib.rs:2167:13 | | 1 | `offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2160:36 | | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | fixtures/reject/src/lib.rs:209:40 | @@ -62,7 +63,7 @@ | 1 | a coercion of `_` to `_`, a pointer to a slice | fixtures/reject/src/lib.rs:1709:28 | | 1 | a coercion to `_` | fixtures/reject/src/lib.rs:291:2 | | 1 | a constant of type `_`, a pointer into memory translation can't name | fixtures/reject/src/lib.rs:773:3 | -| 1 | a copy reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2223:3 | +| 1 | a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2229:3 | | 1 | a dangling pointer to `_`, a value of no bytes made of more than structs and tuples | fixtures/reject/src/lib.rs:732:11 | | 1 | a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is | fixtures/reject/src/lib.rs:77:34 | | 1 | a mapped guard of `_`, which JS can't write in place | fixtures/reject/src/lib.rs:2147:10 | @@ -70,7 +71,7 @@ | 1 | a mutable static, which any code may write | fixtures/reject/src/lib.rs:61:11 | | 1 | a pointer into `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2154:12 | | 1 | a pointer into an array of `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2159:30 | -| 1 | a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2227:27 | +| 1 | a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2233:27 | | 1 | a pointer's address, which JS has no counterpart of | fixtures/reject/src/lib.rs:555:17 | | 1 | a printable type must implement `Display` or `Debug` | fixtures/reject/src/lib.rs:64:1 | | 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | fixtures/reject/src/lib.rs:572:10 | @@ -78,7 +79,7 @@ | 1 | a scalar constant of type `_` | fixtures/reject/src/lib.rs:8:2 | | 1 | a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2158:11 | | 1 | a slice of `()`, which has no bytes, from a pointer | fixtures/reject/src/lib.rs:2167:39 | -| 1 | a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2226:6 | +| 1 | a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2232:6 | | 1 | a trait object of `_`, which is not supported yet | fixtures/reject/src/lib.rs:476:26 | | 1 | an upcast from `_` to `_`, which is not supported yet | fixtures/reject/src/lib.rs:527:32 | | 1 | arithmetic on `_` | fixtures/reject/src/lib.rs:8:2 | @@ -696,8 +697,8 @@ The type or module each refused type or path belongs to.
`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 (2) -- fixtures/reject/src/lib.rs:2205:5: `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/lib.rs:2205:42: `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/lib.rs:2208:5: `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/lib.rs:2209: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
@@ -715,17 +716,17 @@ The type or module each refused type or path belongs to.
-
a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust (2) +
a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust (2) -- fixtures/reject/src/lib.rs:2225:18: a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust -- fixtures/reject/src/lib.rs:2228:21: a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust +- fixtures/reject/src/lib.rs:2231:18: a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust +- fixtures/reject/src/lib.rs:2234:21: a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust
a raw or mutable slice of a byte in no array of bytes (2) -- fixtures/reject/src/lib.rs:2211:12: a raw or mutable slice of a byte in no array of bytes -- fixtures/reject/src/lib.rs:2212:19: a raw or mutable slice of a byte in no array of bytes +- fixtures/reject/src/lib.rs:2217:12: a raw or mutable slice of a byte in no array of bytes +- fixtures/reject/src/lib.rs:2218:19: a raw or mutable slice of a byte in no array of bytes
@@ -861,9 +862,15 @@ The type or module each refused type or path belongs to.
-
`offset_from` between a pointer into a shared subslice of values JS copies and one into an array of this body's own, which JS keeps in different arrays (1) +
`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 (1) -- fixtures/reject/src/lib.rs:2205:88: `offset_from` between a pointer into a shared subslice of values JS copies and one into an array of this body's own, which JS keeps in different arrays +- fixtures/reject/src/lib.rs:2211: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 + +
+ +
`offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays (1) + +- fixtures/reject/src/lib.rs:2210:20: `offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays
@@ -915,9 +922,9 @@ The type or module each refused type or path belongs to.
-
a copy reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust (1) +
a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust (1) -- fixtures/reject/src/lib.rs:2223:3: a copy reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust +- fixtures/reject/src/lib.rs:2229:3: a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust
@@ -965,7 +972,7 @@ The type or module each refused type or path belongs to.
a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust (1) -- fixtures/reject/src/lib.rs:2227:27: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +- fixtures/reject/src/lib.rs:2233:27: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust
@@ -1013,7 +1020,7 @@ The type or module each refused type or path belongs to.
a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust (1) -- fixtures/reject/src/lib.rs:2226:6: a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust +- fixtures/reject/src/lib.rs:2232:6: a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust
diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index eef0882b7..4503aff45 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -207,16 +207,17 @@ a slice of `()`, which has no bytes, from a pointer at fixtures/reject/src/lib.r a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2172:13 a raw pointer to `[u32; 3]` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2183:10 a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2185:10 -`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 at fixtures/reject/src/lib.rs:2205:5 -`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 at fixtures/reject/src/lib.rs:2205:42 -`offset_from` between a pointer into a shared subslice of values JS copies and one into an array of this body's own, which JS keeps in different arrays at fixtures/reject/src/lib.rs:2205:88 -a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2211:12 -a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2212:19 -a copy reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2223:3 -a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2225:18 -a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2226:6 -a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2227:27 -a pointer reaching past a shared subslice of values JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2228:21 +`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 at fixtures/reject/src/lib.rs:2208:5 +`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 at fixtures/reject/src/lib.rs:2209:26 +`offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays at fixtures/reject/src/lib.rs:2210:20 +`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 at fixtures/reject/src/lib.rs:2211:21 +a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2217:12 +a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2218:19 +a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2229:3 +a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2231:18 +a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2232:6 +a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2233:27 +a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2234:21 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:125:2 an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet at fixtures/reject/src/lib.rs:145:1 overriding `Iterator::size_hint`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:173:2 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index 6e0b3d1a2..8e37b0a27 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -2194,15 +2194,21 @@ pub struct Halves { } /// `offset_from` of pointers the body shows JS keeps in different arrays: two made apart -/// of one variable, two of a struct's fields, and one into a shared subslice, a copy, -/// against one into what it was taken of. -pub fn pointers_apart(n: u32, h: Halves) -> isize { +/// of one variable, two of a struct's fields, one into a shared subslice, a copy, against +/// one into what it was taken of, and one into a shared slice of a byte, also a copy, +/// against the byte's. +pub fn pointers_apart(n: u32, h: Halves, byte: u8) -> isize { let a = &raw const n; let b = &raw const n; let lanes = [n; 4]; let part = &lanes[1..3]; + let at = &raw const byte; unsafe { - a.offset_from(b) + (&raw const h.high).offset_from(&raw const h.low) + part.as_ptr().offset_from(lanes.as_ptr()) + let bytes = std::slice::from_raw_parts(at, 1); + a.offset_from(b) + + (&raw const h.high).offset_from(&raw const h.low) + + part.as_ptr().offset_from(lanes.as_ptr()) + + bytes.as_ptr().offset_from(at) } } diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 06d052a47..5bd6628c0 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -18,8 +18,8 @@ use crate::lower::pointer::Pointee; enum Array { /// An array of one, around the place a `&raw` here took, which each `&raw` makes anew. Lone(Location), - /// A shared subslice of values JS copies, which is a copy, made by the call ending this - /// block, with its length if the body fixes it. + /// A shared slice JS copies, of values it copies or of a byte in no array, made by the + /// call ending this block, with its length if the body fixes it. Copy(BasicBlock, Option), /// The array of a variable of this body's own, or of a field of one. Owned, @@ -39,7 +39,7 @@ impl Array { fn what(self) -> &'static str { match self { Array::Lone(_) => "a value in no array", - Array::Copy(..) => "a shared subslice of values JS copies", + Array::Copy(..) => "a shared slice JS copies", Array::Owned => "an array of this body's own", } } @@ -170,10 +170,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { Some(self.moved(self.origin(&args[0].node)?, name.as_str(), &args[1].node)) } ("NonNull", "as_ptr" | "new_unchecked") => self.origin(&args[0].node), - ("slice" | "Vec", "as_ptr" | "as_mut_ptr") => Some(Origin { - array: self.array(&args[0].node)?, - index: Some(0), - }), + ("slice" | "Vec", "as_ptr" | "as_mut_ptr") => self.start(&args[0].node), _ => None, } } @@ -190,14 +187,14 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } } - /// The array the slice or `Vec`, or reference to one, `op` is. - fn array(&self, op: &Operand<'tcx>) -> Option { + /// Where the slice or `Vec`, or reference to one, `op` starts. + fn 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::PointerCoercion(..), op, _) => self.array(op), + Rvalue::Use(op, _) | Rvalue::Cast(mir::CastKind::PointerCoercion(..), op, _) => self.start(op), Rvalue::Ref(_, _, place) | Rvalue::CopyForDeref(place) => match place.projection.as_slice() { - [ProjectionElem::Deref] => self.array(&Operand::Copy(mir::Place::from(place.local))), + [ProjectionElem::Deref] => self.start(&Operand::Copy(mir::Place::from(place.local))), projection if !projection.contains(&ProjectionElem::Deref) && match place.ty(&self.mir.local_decls, self.tcx).ty.kind() { @@ -206,7 +203,10 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { _ => false, } => { - Some(Array::Owned) + Some(Origin { + array: Array::Owned, + index: Some(0), + }) } _ => None, }, @@ -215,21 +215,36 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } let (def_id, gargs, args) = self.def_call(local)?; let (owner, name) = library::key(self.tcx, def_id)?; - let container = gargs.type_at(0).peel_refs(); - // `get_unchecked` is generic over the element, not the slice. + let mut types = gargs.types(); + let container = types.next()?.peel_refs(); + let byte = |ty: ty::Ty<'tcx>| matches!(ty.kind(), ty::Uint(ty::UintTy::U8)); + let copy = |len| { + Some(Origin { + array: Array::Copy(self.def_block(local)?, len), + index: Some(0), + }) + }; + // `get_unchecked` and `from_raw_parts` are generic over the element, not the slice. let elem = match (owner.as_str(), name.as_str(), container.kind()) { + ("raw", "from_raw_parts" | "from_raw_parts_mut", _) => { + let o = self.origin(&args[0].node)?; + // A shared slice of a byte in no array is a copy of it, as no `Uint8Array` can share it. + return match (byte(container), o.array) { + (true, Array::Lone(_)) => copy(self.const_int(&args[1].node)), + _ => Some(o), + }; + } ("slice", "get_unchecked", _) => container, ("Index", "index", ty::Slice(e) | ty::Array(e, _)) => *e, ("Index", "index", ty::Adt(adt, a)) if Std::of(self.tcx, adt.did()) == Some(Std::Vec) => a.type_at(0), _ => return None, }; - let range = gargs.type_at(1); + let range = types.next()?; // A shared range of bytes is a view of them. - if matches!(elem.kind(), ty::Uint(ty::UintTy::U8)) || Int::of(range).is_some() { + if byte(elem) || Int::of(range).is_some() { return None; } - let call = self.def_block(local)?; - Some(Array::Copy(call, self.range_len(&args[1].node, container))) + copy(self.range_len(&args[1].node, container)) } /// How many elements the range `op` takes of `container`, if the body fixes it. diff --git a/ts/tests/pointers.ts b/ts/tests/pointers.ts index 1cf31b621..936343959 100644 --- a/ts/tests/pointers.ts +++ b/ts/tests/pointers.ts @@ -37,12 +37,15 @@ unsure(() => rt.ptrSlice(rt.sliceAsPtr(part), 3)); unsure(() => rt.ptrCopy(rt.sliceAsPtr(part), rt.sliceAsPtr(lanes), 3)); assert.equal(rt.ptrAdd(rt.sliceAsPtr(part), 1).v, 3); -// A byte in no array, as a raw or mutable slice, and as a shared one past it. +// A byte in no array, as a raw or mutable slice, as a shared one past it, and the +// pointer of a shared one, a copy, against the byte's. let byte = 3; const lone = rt.place(() => byte, (v) => (byte = v)); unsure(() => rt.ptrByteSlice(lone, 1, false)); unsure(() => rt.ptrByteSlice(lone, 2, true)); -assert.deepEqual(rt.ptrByteSlice(lone, 1, true).data, Uint8Array.of(3)); +const shared = rt.ptrByteSlice(lone, 1, true); +assert.deepEqual(shared.data, Uint8Array.of(3)); +unsure(() => rt.ptrOffsetFrom(rt.byteSliceAsPtr(shared), lone)); // A write into a `Vec`'s capacity past its length, which a JS array would grow by. const vec: number[] = [7]; From 42894efa7cf3a9cd4d425b2c06554c7b0a942ff6 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 14:55:39 -0800 Subject: [PATCH 10/34] chore(ts): refresh the lang snapshot for a shared part's pointers Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index 0c010c4db..ab8f763fa 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -201,7 +201,7 @@ export function inline(data: rt.ByteSlice, out: rt.ByteVec): void { /** * Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as * the array of one Rust treats it as, and into a mutable part of an array, measured from - * the array, and the pointer an empty slice keeps. + * the array, the pointer an empty slice keeps, and within a shared part, which JS copies. */ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { for (const n of rt.byteSliceView(data)) { @@ -241,6 +241,13 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { rt.iterExtendBytes(out, rt.iterMap(rt.iterSlice(a), (_2: rt.u32): rt.u8 => { return _2 & 0xff; }), undefined, true); + const lanes: rt.u32[] = [n as rt.u32, 4, 5, 6]; + const first: rt.Place = rt.sliceAsPtr(rt.sliceRange(lanes, 1, 3)); + rt.byteVecPush(out, rt.ptrAdd(first, 1).v & 0xff); + rt.byteVecPush(out, rt.ptrOffsetFrom(rt.ptrAdd(first, 2), first) & 0xff); + const _159: rt.u32[] = rt.ptrSlice(rt.ptrAdd(first, 1), 1); + rt.idx(_159, 0); + rt.byteVecPush(out, _159[0] & 0xff); } } From 08da9f2d5c2546e6bf259dfb1c95675c39293602 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:14:15 -0800 Subject: [PATCH 11/34] fix(ts): trace only std's pointer functions, copy unwritten memory, and wrap null The provenance trace matched the crate's own functions named as std's; a copy ran a value's copy over memory nothing wrote; and wrapping arithmetic on a null pointer threw from the lone-value map instead of moving it. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/src/elements.rs | 38 +++++++++++++++++++++++++++++++- ts/runtime/rt.ts | 20 ++++++++++++----- ts/src/lower/mir/origin.rs | 8 +++++-- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index e1ed6fbac..0d45667b9 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -254,7 +254,8 @@ pub fn inline(data: &[u8], out: &mut Vec) { /// Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as /// the array of one Rust treats it as, and into a mutable part of an array, measured from -/// the array, the pointer an empty slice keeps, and within a shared part, which JS copies. +/// the array, the pointer an empty slice keeps, within a shared part, which JS copies, +/// memory nothing wrote copied, null moved and back, and the crate's functions named as std's. pub fn edges(data: &[u8], out: &mut Vec) { for &n in data { let bytes = [n, 1, 2, 3]; @@ -308,6 +309,41 @@ pub fn edges(data: &[u8], out: &mut Vec) { out.push(first.add(2).offset_from(first) as u8); out.push(slice::from_raw_parts(first.add(1), 1)[0] as u8); } + // The crate's own functions named as std's, which are only theirs. + let (x, mut y) = (u32::from(n), n); + out.push(mine::ptr::offset_from(&raw const x, &raw const x) as u8); + out.push(mine::raw::from_raw_parts_mut(&raw mut y, 2)); + // Memory nothing wrote, copied before it is written. + let src = [const { MaybeUninit::>::uninit() }; 2]; + let mut dst = [const { MaybeUninit::>::uninit() }; 2]; + unsafe { + ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), 2); + dst[1].write(if n & 1 == 0 { Ok((x, 1)) } else { Err(n) }); + out.push(match dst[1].assume_init() { + Ok((a, b)) => (a + b) as u8, + Err(e) => e, + }); + } + // A null pointer moved and back, and around wasm32's addresses to null. + let null = ptr::null::(); + out.push(null.wrapping_add(1).wrapping_sub(1).is_null() as u8); + out.push(null.wrapping_add(usize::from(n)).is_null() as u8); + out.push(null.wrapping_add(1 << 30).is_null() as u8); + } +} + +/// Functions of the crate's named as std's pointer functions. +mod mine { + pub mod ptr { + pub fn offset_from(a: *const u32, b: *const u32) -> isize { + unsafe { *a as isize + *b as isize } + } + } + + pub mod raw { + pub fn from_raw_parts_mut(p: *mut u8, n: usize) -> u8 { + unsafe { *p }.wrapping_add(n as u8) + } } } diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 03dc173fc..6a902fc1f 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -159,6 +159,14 @@ export function byteVecAsPtr(v: ByteVec): Place { return new AtIndex(v.data, 0); } +/** What a null pointer, moved by wrapping arithmetic, points into: nothing, which no read reaches. */ +const NOWHERE: never[] = []; + +/** The pointer `index` elements into `array`, null where that is where null is. */ +function indexed(array: { [i: number]: T }, index: number): Place { + return array === NOWHERE && index === 0 ? NULL : new AtIndex(array, index); +} + /** The array of one element that a pointer to a value in no array points into, by its place. */ const LONE = /* @__PURE__ */ new WeakMap, unknown[]>(); @@ -196,6 +204,7 @@ class Lone implements ProxyHandler { */ function elemOf(p: Place): AtIndex { if (p instanceof AtIndex) return p; + if (p === NULL) return new AtIndex(NOWHERE as T[], 0); let lone = LONE.get(p) as T[] | undefined; if (!lone) { lone = new Proxy(new Array(1), new Lone(p)); @@ -219,8 +228,8 @@ function wrapIndex(index: number, n: number, size: number): number { /** `p.add(n)` and `p.offset(n)`: `n` elements past where `p` points. */ export function ptrAdd(p: Place, n: number): Place { if (n === 0) return p; - const at = elemOf(p); - return new AtIndex(at.array, at.index + n); + const e = elemOf(p); + return indexed(e.array, e.index + n); } /** @@ -229,8 +238,8 @@ export function ptrAdd(p: Place, n: number): Place { */ export function ptrWrappingAdd(p: Place, n: number, size: number): Place { if (n === 0) return p; - const at = elemOf(p); - return new AtIndex(at.array, wrapIndex(at.index, n | 0, size)); + const e = elemOf(p); + return indexed(e.array, wrapIndex(e.index, n | 0, size)); } /** `p.offset_from(origin)`: how many elements `p` is past `origin`, in the same array. */ @@ -256,7 +265,8 @@ export function ptrCopy(src: Place, dst: Place, n: number, copy?: (valu return; } const values = Array.from({ length: n }, (_, i) => from.array[from.index + i]); - for (let i = 0; i < n; i++) to.array[to.index + i] = copy ? copy(values[i]) : values[i]; + // Memory nothing wrote yet is copied as it is, which no value's copy can read. + for (let i = 0; i < n; i++) to.array[to.index + i] = copy && values[i] !== undefined ? copy(values[i]) : values[i]; } /** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` elements from `p`. */ diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 5bd6628c0..c76ecfca4 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -63,6 +63,9 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { gargs: GenericArgsRef<'tcx>, args: &[Spanned>], ) -> Option { + if !library::is_std(self.tcx, def_id) { + return None; + } 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()?; @@ -283,7 +286,8 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { pointee(from).is_some() && pointee(from) == pointee(self.mir.local_decls[local].ty) } - /// The function, its generic arguments, and its arguments, of the call that assigns `local`. + /// The function, its generic arguments, and its arguments, of the call of std's that + /// assigns `local`. fn def_call( &self, local: Local, @@ -299,7 +303,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { let ty::FnDef(def_id, gargs) = *func.ty(&self.mir.local_decls, self.tcx).kind() else { return None; }; - Some((def_id, gargs.skip_binder(), args)) + library::is_std(self.tcx, def_id).then_some((def_id, gargs.skip_binder(), args)) } /// The block whose call assigns `local`. From c7beaa586b5cfb6cd70724ff47befbba3733753f Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:33:41 -0800 Subject: [PATCH 12/34] chore(ts): refresh the lang snapshot for the pointer fixes Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.ts | 41 ++++++++++++++++++- .../lang/expected/elements/mine/ptr.ts | 9 ++++ .../lang/expected/elements/mine/raw.ts | 9 ++++ ts/fixtures/lang/expected/lib.ts | 7 ++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 ts/fixtures/lang/expected/elements/mine/ptr.ts create mode 100644 ts/fixtures/lang/expected/elements/mine/raw.ts diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index ab8f763fa..a2e552d7f 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -1,9 +1,11 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { copy$Option_tuple2_u8_u8, copy$array_Option_tuple2_u8_u8, drop$elements_Inline_Option_tuple2_u8_u8, drop$elements_Inline_sync_Arc_u8 } from "./lib.ts"; +import { copy$Option_tuple2_u8_u8, copy$array_Option_tuple2_u8_u8, copy$mem_MaybeUninit_Result_tuple2_u32_u32_u8, drop$elements_Inline_Option_tuple2_u8_u8, drop$elements_Inline_sync_Arc_u8 } from "./lib.ts"; import { thenSome$03chm1q } from "./core/bool.ts"; import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1fmzefe as option_map$1fmzefe, mapOr$1nlkf9b as option_mapOr$1nlkf9b } from "./core/option.ts"; +import { offsetFrom } from "./elements/mine/ptr.ts"; +import { fromRawPartsMut } from "./elements/mine/raw.ts"; import * as Inline from "./elements.Inline.ts"; import * as Storage from "./elements.Storage.ts"; @@ -201,9 +203,12 @@ export function inline(data: rt.ByteSlice, out: rt.ByteVec): void { /** * Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as * the array of one Rust treats it as, and into a mutable part of an array, measured from - * the array, the pointer an empty slice keeps, and within a shared part, which JS copies. + * the array, the pointer an empty slice keeps, within a shared part, which JS copies, + * memory nothing wrote copied, null moved and back, and the crate's functions named as std's. */ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { + let _189!: rt.Result<[rt.u32, rt.u32], rt.u8>; + let _195!: rt.u8; for (const n of rt.byteSliceView(data)) { const bytes: Uint8Array = Uint8Array.of(n, 1, 2, 3); const p: rt.Place = rt.byteSliceAsPtr(rt.byteSliceFrom(bytes)); @@ -248,6 +253,38 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { const _159: rt.u32[] = rt.ptrSlice(rt.ptrAdd(first, 1), 1); rt.idx(_159, 0); rt.byteVecPush(out, _159[0] & 0xff); + const _166: [rt.u32, rt.u8] = [n as rt.u32, n]; + let x: rt.u32 = _166[0]; + let y: rt.u8 = n; + rt.byteVecPush(out, offsetFrom(rt.place(() => x, (value3) => x = value3), rt.place(() => x, (value4) => x = value4)) & 0xff); + rt.byteVecPush(out, fromRawPartsMut(rt.place(() => y, (value5) => y = value5), 2)); + const item = (undefined as unknown) as rt.Result<[rt.u32, rt.u32], rt.u8>; + const item2 = (undefined as unknown) as rt.Result<[rt.u32, rt.u32], rt.u8>; + const dst: rt.Result<[rt.u32, rt.u32], rt.u8>[] = Array.from({ length: 2 }, (): rt.Result<[rt.u32, rt.u32], rt.u8> => { + return copy$mem_MaybeUninit_Result_tuple2_u32_u32_u8(item2); + }); + rt.ptrCopy(rt.sliceAsPtr(Array.from({ length: 2 }, (): rt.Result<[rt.u32, rt.u32], rt.u8> => { + return copy$mem_MaybeUninit_Result_tuple2_u32_u32_u8(item); + })), rt.sliceAsPtr(dst), 2, copy$mem_MaybeUninit_Result_tuple2_u32_u32_u8); + if (!((n & 1) === 0)) { + _189 = rt.Err(n); + } else { + _189 = rt.Ok([x, 1] as [rt.u32, rt.u32]); + } + const uninit = rt.place(() => dst[1], (value6) => dst[1] = value6); + uninit.v = _189; + uninit; + const _196: rt.Result<[rt.u32, rt.u32], rt.u8> = dst[1]; + if (_196.ok) { + _195 = rt.u32Add(_196.value[0], _196.value[1]) & 0xff; + } else { + _195 = _196.error; + } + rt.byteVecPush(out, _195); + const null_: rt.Place = rt.NULL; + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(null_, 1, 4), -1, 4) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(null_, n as rt.usize, 4) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(null_, 1 << 30, 4) === rt.NULL ? 1 : 0); } } diff --git a/ts/fixtures/lang/expected/elements/mine/ptr.ts b/ts/fixtures/lang/expected/elements/mine/ptr.ts new file mode 100644 index 000000000..63614033e --- /dev/null +++ b/ts/fixtures/lang/expected/elements/mine/ptr.ts @@ -0,0 +1,9 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../../rt.ts"; + +export function offsetFrom(a: rt.Place, b: rt.Place): rt.isize { + return rt.isizeAdd(a.v | 0, b.v | 0); +} + +//# sourceMappingURL=ptr.ts.map diff --git a/ts/fixtures/lang/expected/elements/mine/raw.ts b/ts/fixtures/lang/expected/elements/mine/raw.ts new file mode 100644 index 000000000..5534e81f9 --- /dev/null +++ b/ts/fixtures/lang/expected/elements/mine/raw.ts @@ -0,0 +1,9 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../../rt.ts"; + +export function fromRawPartsMut(p: rt.Place, n: rt.usize): rt.u8 { + return rt.u8WrappingAdd(p.v, n & 0xff); +} + +//# sourceMappingURL=raw.ts.map diff --git a/ts/fixtures/lang/expected/lib.ts b/ts/fixtures/lang/expected/lib.ts index a25919d84..c6d16b887 100644 --- a/ts/fixtures/lang/expected/lib.ts +++ b/ts/fixtures/lang/expected/lib.ts @@ -405,6 +405,13 @@ export function copy$mem_MaybeUninit_Option_tuple2_u8_u8(v: [rt.u8, rt.u8] | und return v === undefined ? v : copy$tuple2_u8_u8(v); } +export function copy$mem_MaybeUninit_Result_tuple2_u32_u32_u8(v: rt.Result<[rt.u32, rt.u32], rt.u8>): rt.Result<[rt.u32, rt.u32], rt.u8> { + if (v === undefined) { + return v; + } + return v.ok ? rt.Ok(copy$tuple2_u32_u32(v.value)) : rt.Err(v.error); +} + export function copy$mem_MaybeUninit_array_Option_tuple2_u8_u8(v: Array<[rt.u8, rt.u8] | undefined>): Array<[rt.u8, rt.u8] | undefined> { if (v === undefined) { return v; From 41ee46211d0c68160cafc9e0f9eba54157128867 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:36:36 -0800 Subject: [PATCH 13/34] fix(ts): move null and dangling pointers by address A dangling pointer is at its pointee's alignment, so wrapping arithmetic can take it to null, as it can take null away and back. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/src/elements.rs | 9 +++++++- ts/runtime/rt.ts | 39 ++++++++++++++++++++++++-------- ts/src/lower/pointer.rs | 11 +++++---- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 0d45667b9..22f30485e 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -255,7 +255,8 @@ pub fn inline(data: &[u8], out: &mut Vec) { /// Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as /// the array of one Rust treats it as, and into a mutable part of an array, measured from /// the array, the pointer an empty slice keeps, within a shared part, which JS copies, -/// memory nothing wrote copied, null moved and back, and the crate's functions named as std's. +/// memory nothing wrote copied, null and dangling pointers moved by address, and the crate's +/// functions named as std's. pub fn edges(data: &[u8], out: &mut Vec) { for &n in data { let bytes = [n, 1, 2, 3]; @@ -329,6 +330,12 @@ pub fn edges(data: &[u8], out: &mut Vec) { out.push(null.wrapping_add(1).wrapping_sub(1).is_null() as u8); out.push(null.wrapping_add(usize::from(n)).is_null() as u8); out.push(null.wrapping_add(1 << 30).is_null() as u8); + // A dangling pointer, at its alignment, moved to null and past it. + let dangling = NonNull::::dangling().as_ptr(); + out.push(dangling.wrapping_sub(1).is_null() as u8); + out.push(dangling.wrapping_add(usize::MAX).is_null() as u8); + out.push(dangling.wrapping_sub(usize::from(n)).wrapping_add(usize::from(n)).is_null() as u8); + out.push(NonNull::::dangling().as_ptr().wrapping_offset(isize::from(n % 3) - 1).is_null() as u8); } } diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 6a902fc1f..37e1d25b4 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -159,12 +159,25 @@ export function byteVecAsPtr(v: ByteVec): Place { return new AtIndex(v.data, 0); } -/** What a null pointer, moved by wrapping arithmetic, points into: nothing, which no read reaches. */ -const NOWHERE: never[] = []; +/** A pointer wrapping arithmetic moved off null or a dangling one: an address no memory is at. */ +class Address implements Place { + readonly addr: number; -/** The pointer `index` elements into `array`, null where that is where null is. */ -function indexed(array: { [i: number]: T }, index: number): Place { - return array === NOWHERE && index === 0 ? NULL : new AtIndex(array, index); + constructor(addr: number) { + this.addr = addr; + } + + get v(): never { + return nowhere(); + } + + set v(_: never) { + nowhere(); + } +} + +function nowhere(): never { + throw new Error("rust2-ts: a read or write through a pointer to no memory"); } /** The array of one element that a pointer to a value in no array points into, by its place. */ @@ -204,7 +217,7 @@ class Lone implements ProxyHandler { */ function elemOf(p: Place): AtIndex { if (p instanceof AtIndex) return p; - if (p === NULL) return new AtIndex(NOWHERE as T[], 0); + if (p === NULL) return new AtIndex([] as T[], 0); let lone = LONE.get(p) as T[] | undefined; if (!lone) { lone = new Proxy(new Array(1), new Lone(p)); @@ -229,17 +242,23 @@ function wrapIndex(index: number, n: number, size: number): number { export function ptrAdd(p: Place, n: number): Place { if (n === 0) return p; const e = elemOf(p); - return indexed(e.array, e.index + n); + return new AtIndex(e.array, e.index + n); } /** * `p.wrapping_add(n)` and `wrapping_offset`, `n` an offset of `size`-byte elements read - * as wasm32's `isize`, wrapping as a 32-bit address does. + * as wasm32's `isize`, wrapping as a 32-bit address does. Null is at address 0, and a + * dangling pointer at `align`, which the arithmetic moves by address. */ -export function ptrWrappingAdd(p: Place, n: number, size: number): Place { +export function ptrWrappingAdd(p: Place, n: number, size: number, align: number): Place { if (n === 0) return p; + if (p === NULL || p === DANGLING || p instanceof Address) { + const base = p === NULL ? 0 : p === DANGLING ? align : (p as Address).addr; + const addr = Number((BigInt(base) + BigInt(n | 0) * BigInt(size)) & 0xffffffffn); + return addr === 0 ? NULL : addr === align ? DANGLING : (new Address(addr) as Place); + } const e = elemOf(p); - return indexed(e.array, wrapIndex(e.index, n | 0, size)); + return new AtIndex(e.array, wrapIndex(e.index, n | 0, size)); } /** `p.offset_from(origin)`: how many elements `p` is past `origin`, in the same array. */ diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 93b198fcc..96b1d3a64 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -649,10 +649,13 @@ pub fn call<'tcx>( _ => n, }; match name.starts_with("wrapping") { - true => { - let size = super::iter::size_of(b, pointee(), span); - Expr::rt("ptrWrappingAdd", vec![p, n, size]) - } + true => match super::iter::layout(b, pointee(), span) { + Ok((size, align)) => Expr::rt( + "ptrWrappingAdd", + vec![p, n, Expr::lit(size.to_string()), Expr::lit(align.to_string())], + ), + Err(e) => e, + }, false => Expr::rt("ptrAdd", vec![p, n]), } } From 923432eb4dd4c6411f606b1c3fd893de335a99b6 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:38:24 -0800 Subject: [PATCH 14/34] style(ts): format the dangling cases Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/src/elements.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 22f30485e..3879d27b8 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -334,8 +334,18 @@ pub fn edges(data: &[u8], out: &mut Vec) { let dangling = NonNull::::dangling().as_ptr(); out.push(dangling.wrapping_sub(1).is_null() as u8); out.push(dangling.wrapping_add(usize::MAX).is_null() as u8); - out.push(dangling.wrapping_sub(usize::from(n)).wrapping_add(usize::from(n)).is_null() as u8); - out.push(NonNull::::dangling().as_ptr().wrapping_offset(isize::from(n % 3) - 1).is_null() as u8); + out.push( + dangling + .wrapping_sub(usize::from(n)) + .wrapping_add(usize::from(n)) + .is_null() as u8, + ); + out.push( + NonNull::::dangling() + .as_ptr() + .wrapping_offset(isize::from(n % 3) - 1) + .is_null() as u8, + ); } } From fadfdfa20df1370daa4f186551699c5da079cd99 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:53:39 -0800 Subject: [PATCH 15/34] chore(ts): refresh the lang snapshot for dangling pointers Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.ts | 28 ++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index a2e552d7f..6e2bc46af 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -29,9 +29,9 @@ export function vecs(data: rt.ByteSlice, out: rt.ByteVec): void { emit(rt.ptrSlice(p, len), out); const end: rt.Place = rt.ptrAdd(p, len); const last: rt.Place = rt.ptrAdd(end, -1); - const _54: rt.Place = rt.ptrWrappingAdd(last, -1, 4); + const _54: rt.Place = rt.ptrWrappingAdd(last, -1, 4, 4); _54.v = rt.u32Add(_54.v, 1); - const _56: rt.Place = rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, 1, 4), -1, 4); + const _56: rt.Place = rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, 1, 4, 4), -1, 4, 4); _56.v = rt.u32Add(_56.v, 5); rt.byteVecPush(out, rt.ptrOffsetFrom(end, p) & 0xff); rt.byteVecPush(out, rt.ptrOffsetFrom(p, last) & 0xff); @@ -204,7 +204,8 @@ export function inline(data: rt.ByteSlice, out: rt.ByteVec): void { * Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as * the array of one Rust treats it as, and into a mutable part of an array, measured from * the array, the pointer an empty slice keeps, within a shared part, which JS copies, - * memory nothing wrote copied, null moved and back, and the crate's functions named as std's. + * memory nothing wrote copied, null and dangling pointers moved by address, and the crate's + * functions named as std's. */ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { let _189!: rt.Result<[rt.u32, rt.u32], rt.u8>; @@ -213,11 +214,11 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { const bytes: Uint8Array = Uint8Array.of(n, 1, 2, 3); const p: rt.Place = rt.byteSliceAsPtr(rt.byteSliceFrom(bytes)); const q: rt.Place = rt.sliceAsPtr([n as rt.u16, 7, 8]); - rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, 4294967295, 1), 1, 1).v); - rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, 2147483648, 1), 2147483648, 1).v); - rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, -4294967295, 1), -1, 1).v); - rt.iterExtendBytes(out, rt.iterSlice(rt.u16ToBeBytes(rt.ptrWrappingAdd(rt.ptrWrappingAdd(q, 2147483648, 2), 1, 2).v).reverse()), undefined, true); - rt.iterExtendBytes(out, rt.iterSlice(rt.u16ToBeBytes(rt.ptrWrappingAdd(rt.ptrWrappingAdd(rt.ptrWrappingAdd(q, 3221225472, 2), 1 << 30, 2), 2, 2).v).reverse()), undefined, true); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, 4294967295, 1, 1), 1, 1, 1).v); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, 2147483648, 1, 1), 2147483648, 1, 1).v); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(p, -4294967295, 1, 1), -1, 1, 1).v); + rt.iterExtendBytes(out, rt.iterSlice(rt.u16ToBeBytes(rt.ptrWrappingAdd(rt.ptrWrappingAdd(q, 2147483648, 2, 2), 1, 2, 2).v).reverse()), undefined, true); + rt.iterExtendBytes(out, rt.iterSlice(rt.u16ToBeBytes(rt.ptrWrappingAdd(rt.ptrWrappingAdd(rt.ptrWrappingAdd(q, 3221225472, 2, 2), 1 << 30, 2, 2), 2, 2, 2).v).reverse()), undefined, true); let lone: rt.u32 = rt.u32Add(n as rt.u32, 42); const at: rt.Place = rt.place(() => lone, (value) => lone = value); const _63: rt.u32[] = rt.ptrSlice(at, 1); @@ -282,9 +283,14 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { } rt.byteVecPush(out, _195); const null_: rt.Place = rt.NULL; - rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(null_, 1, 4), -1, 4) === rt.NULL ? 1 : 0); - rt.byteVecPush(out, rt.ptrWrappingAdd(null_, n as rt.usize, 4) === rt.NULL ? 1 : 0); - rt.byteVecPush(out, rt.ptrWrappingAdd(null_, 1 << 30, 4) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(null_, 1, 4, 4), -1, 4, 4) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(null_, n as rt.usize, 4, 4) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(null_, 1 << 30, 4, 4) === rt.NULL ? 1 : 0); + const dangling: rt.Place = rt.DANGLING; + rt.byteVecPush(out, rt.ptrWrappingAdd(dangling, -1, 2, 2) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(dangling, 4294967295, 2, 2) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(dangling, -((n as rt.usize)), 2, 2), n as rt.usize, 2, 2) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.DANGLING, rt.isizeSub(rt.u8Rem(n, 3) as rt.isize, 1), 8, 8) === rt.NULL ? 1 : 0); } } From 8a6e1f3e7bf23e9de076f2d6a2821cbbe3f00442 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:54:34 -0800 Subject: [PATCH 16/34] fix(ts): measure equal addresses as none apart Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/src/elements.rs | 3 +++ ts/runtime/rt.ts | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 3879d27b8..f6f368cfb 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -330,6 +330,9 @@ pub fn edges(data: &[u8], out: &mut Vec) { out.push(null.wrapping_add(1).wrapping_sub(1).is_null() as u8); out.push(null.wrapping_add(usize::from(n)).is_null() as u8); out.push(null.wrapping_add(1 << 30).is_null() as u8); + // Two pointers at the same address off null, which are none apart. + let (p, q) = (null.wrapping_add(usize::from(n) + 1), null.wrapping_add(usize::from(n) + 1)); + out.push(unsafe { p.offset_from(q) } as u8); // A dangling pointer, at its alignment, moved to null and past it. let dangling = NonNull::::dangling().as_ptr(); out.push(dangling.wrapping_sub(1).is_null() as u8); diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 37e1d25b4..ec1e526e3 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -263,7 +263,8 @@ export function ptrWrappingAdd(p: Place, n: number, size: number, align: n /** `p.offset_from(origin)`: how many elements `p` is past `origin`, in the same array. */ export function ptrOffsetFrom(p: Place, origin: Place): number { - if (p === origin) return 0; + // Equal addresses are none apart, in no allocation as in one. + if (p === origin || (p instanceof Address && origin instanceof Address && p.addr === origin.addr)) return 0; const [a, b] = [elemOf(p), elemOf(origin)]; if (a.array !== b.array) throw new Error("rust2-ts: `offset_from` of pointers JS keeps in different arrays"); return a.index - b.index; From 16d086bdff0db2b95265ebecb53b70983ea32f5f Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:55:31 -0800 Subject: [PATCH 17/34] style(ts): format the equal addresses case Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/src/elements.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index f6f368cfb..d90a8adb5 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -331,7 +331,10 @@ pub fn edges(data: &[u8], out: &mut Vec) { out.push(null.wrapping_add(usize::from(n)).is_null() as u8); out.push(null.wrapping_add(1 << 30).is_null() as u8); // Two pointers at the same address off null, which are none apart. - let (p, q) = (null.wrapping_add(usize::from(n) + 1), null.wrapping_add(usize::from(n) + 1)); + let (p, q) = ( + null.wrapping_add(usize::from(n) + 1), + null.wrapping_add(usize::from(n) + 1), + ); out.push(unsafe { p.offset_from(q) } as u8); // A dangling pointer, at its alignment, moved to null and past it. let dangling = NonNull::::dangling().as_ptr(); From 19bb6eeb9b63d05710fa312e783a8a337c0d671c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:10:07 -0800 Subject: [PATCH 18/34] chore(ts): refresh the lang snapshot for equal addresses Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index 6e2bc46af..2388fd6b2 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -286,6 +286,8 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(null_, 1, 4, 4), -1, 4, 4) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(null_, n as rt.usize, 4, 4) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(null_, 1 << 30, 4, 4) === rt.NULL ? 1 : 0); + const _226: [rt.Place, rt.Place] = [rt.ptrWrappingAdd(null_, rt.usizeAdd(n as rt.usize, 1), 4, 4), rt.ptrWrappingAdd(null_, rt.usizeAdd(n as rt.usize, 1), 4, 4)]; + rt.byteVecPush(out, rt.ptrOffsetFrom(_226[0], _226[1]) & 0xff); const dangling: rt.Place = rt.DANGLING; rt.byteVecPush(out, rt.ptrWrappingAdd(dangling, -1, 2, 2) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(dangling, 4294967295, 2, 2) === rt.NULL ? 1 : 0); From 1fff10c8851f480561a0bb51df47481886fab357 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:23:43 -0800 Subject: [PATCH 19/34] chore(ts): refresh the snapshots after merging main Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/harness.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index b208d2561..e11bdd2e6 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -23,6 +23,7 @@ import { defaults as defaults_defaults } from "./defaults.ts"; import { durations } from "./durations.ts"; import { dyns, shared as dyns_shared, anys, fns } from "./dyns.ts"; import { edits as edits_edits, bytes as edits_bytes } from "./edits.ts"; +import { vecs as elements_vecs, arrays as elements_arrays, segments as elements_segments, inline, edges as elements_edges } from "./elements.ts"; import { eqs } from "./errors.ts"; import { fields as fields_fields } from "./fields.ts"; import { floats, mulAdds as floats_mulAdds, formats as floats_formats, parses as floats_parses } from "./floats.ts"; @@ -44,7 +45,7 @@ import { u8s as methods_u8s, u16s as methods_u16s, u32s as methods_u32s, u64s as import { nested as nested_nested } from "./nested.ts"; import { objects } from "./objects.ts"; import { orders, bytes as orders_bytes, tuples as orders_tuples, held as orders_held, btrees } from "./orders.ts"; -import { lists as pointers_lists, shared as pointers_shared, places as pointers_places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound as pointers_rebound, lapsed, writers, made as pointers_made, segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken as pointers_taken, bytes as pointers_bytes, loaded, reattached, partWritten, aimed } from "./pointers.ts"; +import { lists as pointers_lists, shared as pointers_shared, places as pointers_places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound as pointers_rebound, lapsed, writers, made as pointers_made, segments as pointers_segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken as pointers_taken, bytes as pointers_bytes, loaded, reattached, partWritten, aimed } from "./pointers.ts"; import { refs, toRefs } from "./refs.ts"; import { results as results_results } from "./results.ts"; import { returns } from "./returns.ts"; @@ -224,7 +225,7 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::slices", cows_slices as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::bytes", cows_bytes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u8s", wraps_u8s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u16s", wraps_u16s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u32s", wraps_u32s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u64s", wraps_u64s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u128s", wraps_u128s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::usizes", wraps_usizes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i8s", wraps_i8s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i16s", wraps_i16s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i32s", wraps_i32s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i64s", wraps_i64s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i128s", wraps_i128s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::isizes", wraps_isizes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::enums", (_2174: rt.u8, data74: rt.ByteSlice, out75: rt.ByteVec): undefined => { enums(data74, out75); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::segments", (_2175: rt.u8, data75: rt.ByteSlice, out76: rt.ByteVec): undefined => { - segments(data75, out76); + pointers_segments(data75, out76); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::uninit_runs", (_2176: rt.u8, data76: rt.ByteSlice, out77: rt.ByteVec): undefined => { uninitRuns(data76, out77); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::beside_written", (_2177: rt.u8, data77: rt.ByteSlice, out78: rt.ByteVec): undefined => { @@ -277,6 +278,16 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) downgraded(data96, out97); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["borrows::leaks", (_2197: rt.u8, data97: rt.ByteSlice, out98: rt.ByteVec): undefined => { leaks(data97, out98); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::vecs", (_2198: rt.u8, data98: rt.ByteSlice, out99: rt.ByteVec): undefined => { + elements_vecs(data98, out99); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::arrays", (_2199: rt.u8, data99: rt.ByteSlice, out100: rt.ByteVec): undefined => { + elements_arrays(data99, out100); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::segments", (_2200: rt.u8, data100: rt.ByteSlice, out101: rt.ByteVec): undefined => { + elements_segments(data100, out101); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::inline", (_2201: rt.u8, data101: rt.ByteSlice, out102: rt.ByteVec): undefined => { + inline(data101, out102); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::edges", (_2202: rt.u8, data102: rt.ByteSlice, out103: rt.ByteVec): undefined => { + elements_edges(data102, out103); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } From f1ff2f4c072b7420254f1052d85244888e08e550 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 07:11:11 -0800 Subject: [PATCH 20/34] test(ts): accept wasm32's fma a last place off where the translation is exact Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 + quest/m3/ts-fma-allowance.md | 15 +++++ ts/fixtures/lang/caller.ts | 4 +- ts/fixtures/lang/expected/floats.ts | 72 +++++++++++++---------- ts/fixtures/lang/fma.ts | 72 +++++++++++++++++++++++ ts/fixtures/lang/src/floats.rs | 13 ++++- ts/tests/fixtures.rs | 7 ++- ts/tests/fma.ts | 88 +++++++++++++++++++++++++++++ ts/tests/runtime.rs | 12 ++++ 9 files changed, 247 insertions(+), 37 deletions(-) create mode 100644 quest/m3/ts-fma-allowance.md create mode 100644 ts/fixtures/lang/fma.ts create mode 100644 ts/tests/fma.ts diff --git a/quest/m3/README.md b/quest/m3/README.md index 5c3286027..068eb3a11 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -19,6 +19,7 @@ generic std gap is planned here even when moq never calls it. - [The capacity of a BytesMut](/quest/m3/ts-bytes-capacity.md) - `BytesMut::capacity` and what reads it translate with `bytes`' values - [Trait objects](/quest/m3/ts-dyn.md) - `dyn Error`, `dyn Iterator`, `dyn Buf`, and `Arc` translate, so a consumer neither rewrites nor binds them - [Placeholders written to a crate's own fmt::Write](/quest/m3/ts-fmt-calls.md) - `write!` with placeholders into a crate's own writer translates, its `write_str` seeing each call std makes +- [Waiting: drop the lang fixture's mul_add allowance](/quest/m3/ts-fma-allowance.md) - the differential fails on any `mul_add` difference again once wasm32's `fma` rounds exactly - [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library - [Drop order in std's folds and iterators](/quest/m3/ts-drop-order.md) - the values that drop still refused in std's consumers, flattens, and collects drop as std's do - [A slice of one value behind any &mut](/quest/m3/ts-slice-of-one.md) - `slice::from_mut` and `array::from_mut` of a `&mut u8`, or of an object no place names, translate diff --git a/quest/m3/ts-fma-allowance.md b/quest/m3/ts-fma-allowance.md new file mode 100644 index 000000000..257e965a4 --- /dev/null +++ b/quest/m3/ts-fma-allowance.md @@ -0,0 +1,15 @@ +# [XS] Waiting: drop the lang fixture's mul_add allowance + +## Goal + +Waits on upstream. wasm32's `fma`, compiler-builtins' generic libm, rounds some +near-ties a last place off +([rust-lang/compiler-builtins#1353](https://github.com/rust-lang/compiler-builtins/issues/1353)), +so the lang fixture's differential accepts a `floats::mul_adds` result where the +translation is the exactly rounded one and native is a last place from it +(`ts/fixtures/lang/fma.ts`). + +Once [rust-lang/compiler-builtins#1358](https://github.com/rust-lang/compiler-builtins/pull/1358) +ships in the compiler-builtins that the pinned nightly in `ts/rust-toolchain.toml` +builds, delete `fma.ts`, its test `ts/tests/fma.ts`, and `within`'s call to it, so +any difference fails again, and delete this quest. diff --git a/ts/fixtures/lang/caller.ts b/ts/fixtures/lang/caller.ts index 5e12e45c1..d61121cf6 100644 --- a/ts/fixtures/lang/caller.ts +++ b/ts/fixtures/lang/caller.ts @@ -3,6 +3,7 @@ // which nothing translated calls. `differential.ts` runs it beside native Rust's // `harness::entry`, from the output directory. +import { mulAddsWithin } from "./fma.ts"; import * as Fault from "./fmt.Fault.ts"; import { cases as named, run as harness, op as pick } from "./harness.ts"; import * as rt from "./rt.ts"; @@ -231,7 +232,7 @@ const mulAdds = (max: number, tiny: number, eps: number, op: number[], bytes: (x const floatsMulAdds = mulAdds(Number.MAX_VALUE, 5e-324, 2 ** -52, op("floats::mul_adds"), double); const singlesMulAdds = mulAdds(3.4028234663852886e38, 2 ** -149, 2 ** -23, op("singles::mul_adds"), single); -const [floatsAt, singlesAt] = [pick("floats::floats"), pick("singles::singles")]; +const [floatsAt, singlesAt, floatsMulAddsAt] = [pick("floats::floats"), pick("singles::singles"), pick("floats::mul_adds")]; /** How many ULPs from native Rust's each float of `floats`' and `singles`' kind 7 may be, which Rust leaves to libm. */ const ULPS = 2n; @@ -240,6 +241,7 @@ export function within(input: Uint8Array, native: Uint8Array, translated: Uint8A // A first byte of 0xff prints a `Fault`, and picks no case. if (input[0] === 0xff) return false; const at = ((input[0] << 8) | input[1]) % count; + if (at === floatsMulAddsAt) return mulAddsWithin(native, translated); const width = at === floatsAt ? 8 : at === singlesAt ? 4 : 0; if (width === 0 || input[2] % 10 !== 7 || native.length !== translated.length) return false; const [a, b] = [native, translated].map((v) => new DataView(v.buffer, v.byteOffset, v.length)); diff --git a/ts/fixtures/lang/expected/floats.ts b/ts/fixtures/lang/expected/floats.ts index 3417c6ae3..e1411a2db 100644 --- a/ts/fixtures/lang/expected/floats.ts +++ b/ts/fixtures/lang/expected/floats.ts @@ -270,11 +270,12 @@ export function floats(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { * `mul_add`, rounded once, of three operands, or of 32 sets made from a seed: any bits, * a product's error alone, half the product's last place, where rounding the product * first would tie, a product of half `c`'s last place, subnormal sums, products past `f64::MAX` that the sum brings back, and - * each side of where the runtime's fast path ends. + * each side of where the runtime's fast path ends. Each result follows its operands, which + * `fma.ts` rounds exactly where native's differs. */ export function mulAdds(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { - let _53!: [rt.f64, rt.f64, rt.f64]; - let _103!: rt.f64; + let _52!: [rt.f64, rt.f64, rt.f64]; + let _102!: rt.f64; let ea4!: rt.i64; b0: { if (!(rt.u8Rem(kind, 2) === 0)) { @@ -294,60 +295,71 @@ export function mulAdds(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void const a2: rt.f64 = near(step(rt.place(() => state, (value2) => state = value2)), ea); const b2: rt.f64 = near(step(rt.place(() => state, (value3) => state = value3)), rt.i64Sub(e, ea)); const c2: rt.u64 = step(rt.place(() => state, (value4) => state = value4)); - const _54: rt.u64 = rt.u64Rem(r, 6); - if (_54 === 0) { - _53 = [rt.f64FromBits(r), rt.f64FromBits(step(rt.place(() => state, (value5) => state = value5))), rt.f64FromBits(c2)] as [rt.f64, rt.f64, rt.f64]; - } else if (_54 === 1) { - _53 = [a2, b2, -(a2 * b2)] as [rt.f64, rt.f64, rt.f64]; - } else if (_54 === 2) { + const _53: rt.u64 = rt.u64Rem(r, 6); + if (_53 === 0) { + _52 = [rt.f64FromBits(r), rt.f64FromBits(step(rt.place(() => state, (value5) => state = value5))), rt.f64FromBits(c2)] as [rt.f64, rt.f64, rt.f64]; + } else if (_53 === 1) { + _52 = [a2, b2, -(a2 * b2)] as [rt.f64, rt.f64, rt.f64]; + } else if (_53 === 2) { if (!(rt.u64Shr(r, 63) === 0)) { const c4: rt.f64 = near(c2, e); - _53 = [a2, (rt.f64NextUp(Math.abs(c4)) - Math.abs(c4)) * 0.5 / a2, c4] as [rt.f64, rt.f64, rt.f64]; + _52 = [a2, (rt.f64NextUp(Math.abs(c4)) - Math.abs(c4)) * 0.5 / a2, c4] as [rt.f64, rt.f64, rt.f64]; } else { const p: rt.f64 = Math.abs(a2 * b2); - const _71: rt.usize = rt.i64Low(pick(24, 5)); - if (!(_71 < 5)) { - rt.panic("index out of bounds: the len is " + 5 + " but the index is " + _71); + const _70: rt.usize = rt.i64Low(pick(24, 5)); + if (!(_70 < 5)) { + rt.panic("index out of bounds: the len is " + 5 + " but the index is " + _70); } - const half: rt.f64 = [0.5, -0.5, 0.25, -0.75, 1.5][_71]; - _53 = [a2, b2, (rt.f64NextUp(p) - p) * half] as [rt.f64, rt.f64, rt.f64]; + const half: rt.f64 = [0.5, -0.5, 0.25, -0.75, 1.5][_70]; + _52 = [a2, b2, (rt.f64NextUp(p) - p) * half] as [rt.f64, rt.f64, rt.f64]; } - } else if (_54 === 3) { + } else if (_53 === 3) { const ea2: rt.i64 = rt.i64Sub(pick(24, 1000), 500); const e2: rt.i64 = rt.i64Sub(-1000, pick(40, 100)); - const _98: [rt.f64, rt.f64] = [near(r, ea2), near(c2, rt.i64Sub(e2, ea2))]; - const a4: rt.f64 = _98[0]; - const b4: rt.f64 = _98[1]; + const _97: [rt.f64, rt.f64] = [near(r, ea2), near(c2, rt.i64Sub(e2, ea2))]; + const a4: rt.f64 = _97[0]; + const b4: rt.f64 = _97[1]; if (!(rt.u64Shr(r, 63) === 0)) { - _103 = near(rt.u64Shr(c2, 1), rt.i64Sub(rt.i64Add(e2, pick(48, 60)), 30)); + _102 = near(rt.u64Shr(c2, 1), rt.i64Sub(rt.i64Add(e2, pick(48, 60)), 30)); } else { - _103 = -(a4 * b4); + _102 = -(a4 * b4); } - _53 = [a4, b4, _103] as [rt.f64, rt.f64, rt.f64]; - } else if (_54 === 4) { + _52 = [a4, b4, _102] as [rt.f64, rt.f64, rt.f64]; + } else if (_53 === 4) { const ea3: rt.i64 = rt.i64Add(990, pick(24, 60)); const a5: rt.f64 = near(r, ea3); const b5: rt.f64 = near(c2, rt.i64Sub(rt.i64Add(rt.i64Sub(1023, ea3), pick(40, 4)), 1)); - _53 = [a5, b5, rt.f64Copysign(near(rt.u64Shr(c2, 1), rt.i64Add(1020, pick(48, 4))), -(a5 * b5))] as [rt.f64, rt.f64, rt.f64]; + _52 = [a5, b5, rt.f64Copysign(near(rt.u64Shr(c2, 1), rt.i64Add(1020, pick(48, 4))), -(a5 * b5))] as [rt.f64, rt.f64, rt.f64]; } else { if (!(rt.u64Shr(r, 63) === 0)) { ea4 = rt.i64Sub(pick(24, 1000), 500); } else { ea4 = rt.i64Add(990, pick(24, 10)); } - const _162: [rt.f64, rt.f64] = [near(r, ea4), near(c2, rt.i64Sub(rt.i64Sub(rt.i64Add(-968, pick(40, 8)), 4), ea4))]; - const a6: rt.f64 = _162[0]; - const b6: rt.f64 = _162[1]; + const _161: [rt.f64, rt.f64] = [near(r, ea4), near(c2, rt.i64Sub(rt.i64Sub(rt.i64Add(-968, pick(40, 8)), 4), ea4))]; + const a6: rt.f64 = _161[0]; + const b6: rt.f64 = _161[1]; const p2: rt.f64 = Math.abs(a6 * b6); - _53 = [a6, b6, (rt.f64NextUp(p2) - p2) * 0.5] as [rt.f64, rt.f64, rt.f64]; + _52 = [a6, b6, (rt.f64NextUp(p2) - p2) * 0.5] as [rt.f64, rt.f64, rt.f64]; } - putF64(out, rt.f64MulAdd(_53[0], _53[1], _53[2])); + putMulAdd(out, _52[0], _52[1], _52[2]); } } const _9: [rt.f64, rt.ByteSlice] = take(data); const a: rt.f64 = _9[0]; const _12: [rt.f64, rt.ByteSlice] = take(_9[1]); - putF64(out, rt.f64MulAdd(a, _12[0], take(_12[1])[0])); + putMulAdd(out, a, _12[0], take(_12[1])[0]); + } +} + +export function putMulAdd(out: rt.ByteVec, a: rt.f64, b: rt.f64, c: rt.f64): void { + const iter: rt.Iter = rt.iterSlice([a, b, c, rt.f64MulAdd(a, b, c)]); + while (true) { + const _9: rt.f64 | rt.Done = iter.next(); + if (_9 === rt.DONE) { + break; + } + putF64(out, _9); } } diff --git a/ts/fixtures/lang/fma.ts b/ts/fixtures/lang/fma.ts new file mode 100644 index 000000000..ab510650e --- /dev/null +++ b/ts/fixtures/lang/fma.ts @@ -0,0 +1,72 @@ +// The one difference from native Rust that the lang fixture accepts in `floats::mul_adds`: +// wasm32's `fma`, compiler-builtins' generic libm, rounds some near-ties a last place off +// (rust-lang/compiler-builtins#1353, fixed by #1358). `/quest/m3/ts-fma-allowance.md` +// removes this once the toolchain ships the fix. + +/** A finite double as `m * 2^e`, exactly. */ +function parts(x: number): { m: bigint; e: number } { + const view = new DataView(new ArrayBuffer(8)); + view.setFloat64(0, x); + const bits = view.getBigUint64(0); + const biased = Number((bits >> 52n) & 0x7ffn); + const frac = bits & ((1n << 52n) - 1n); + const m = biased === 0 ? frac : frac | (1n << 52n); + return { m: bits >> 63n === 1n ? -m : m, e: Math.max(biased, 1) - 1075 }; +} + +/** `a * b + c` rounded once to nearest, ties to even, from exact integers; `undefined` if an operand is not finite. */ +export function exactMulAdd(a: number, b: number, c: number): number | undefined { + if (![a, b, c].every(Number.isFinite)) return undefined; + const [pa, pb, pc] = [parts(a), parts(b), parts(c)]; + const [mp, ep] = [pa.m * pb.m, pa.e + pb.e]; + const e = Math.min(ep, pc.e); + const sum = (mp << BigInt(ep - e)) + (pc.m << BigInt(pc.e - e)); + // An exact zero is -0 only if the product and `c` both are, so of zeros JS's sum is exact. + if (sum === 0n) return a === 0 || b === 0 ? a * b + c : 0; + const sign = sum < 0n ? -1 : 1; + const mag = sum < 0n ? -sum : sum; + // The last place of the result: 53 bits below its leading one, or a subnormal's. + const lead = mag.toString(2).length - 1 + e; + const q = Math.max(lead - 52, -1074); + let kept = mag; + if (q > e) { + const shift = BigInt(q - e); + kept = mag >> shift; + const rest = mag - (kept << shift); + const half = 1n << (shift - 1n); + if (rest > half || (rest === half && (kept & 1n) === 1n)) kept += 1n; + } else { + kept = mag << BigInt(e - q); + } + // `kept` is at most 2^53 and `2^q` a double, so this product rounds only past the range, to infinity. + return sign * Number(kept) * 2 ** q; +} + +/** A finite double's bits as a line through zero, so the distance between two is how many doubles apart they are. */ +function line(x: number): bigint { + const view = new DataView(new ArrayBuffer(8)); + view.setFloat64(0, x); + const bits = view.getBigInt64(0); + return bits < 0n ? -(bits & ((1n << 63n) - 1n)) : bits; +} + +/** + * Whether `mul_adds`' outputs, each result after its operands as four big-endian doubles, + * differ only where the translation's result is the exactly rounded one and native's is + * a last place from it. + */ +export function mulAddsWithin(native: Uint8Array, translated: Uint8Array): boolean { + if (native.length !== translated.length || native.length % 32 !== 0) return false; + const [n, t] = [native, translated].map((v) => new DataView(v.buffer, v.byteOffset, v.length)); + for (let i = 0; i < native.length; i += 32) { + const differs = (from: number, to: number) => native.subarray(from, to).some((x, j) => x !== translated[from + j]); + if (differs(i, i + 24)) return false; + if (!differs(i + 24, i + 32)) continue; + const [a, b, c, want, got] = [t.getFloat64(i), t.getFloat64(i + 8), t.getFloat64(i + 16), n.getFloat64(i + 24), t.getFloat64(i + 24)]; + const exact = exactMulAdd(a, b, c); + if (exact === undefined || !Object.is(got, exact) || !Number.isFinite(want) || !Number.isFinite(got)) return false; + const d = line(want) - line(got); + if (d !== 1n && d !== -1n) return false; + } + return true; +} diff --git a/ts/fixtures/lang/src/floats.rs b/ts/fixtures/lang/src/floats.rs index 666321cd6..0e2d2e056 100644 --- a/ts/fixtures/lang/src/floats.rs +++ b/ts/fixtures/lang/src/floats.rs @@ -314,13 +314,14 @@ pub fn floats(kind: u8, data: &[u8], out: &mut Vec) { /// `mul_add`, rounded once, of three operands, or of 32 sets made from a seed: any bits, /// a product's error alone, half the product's last place, where rounding the product /// first would tie, a product of half `c`'s last place, subnormal sums, products past `f64::MAX` that the sum brings back, and -/// each side of where the runtime's fast path ends. +/// each side of where the runtime's fast path ends. Each result follows its operands, which +/// `fma.ts` rounds exactly where native's differs. pub fn mul_adds(kind: u8, data: &[u8], out: &mut Vec) { if kind % 2 == 0 { let (a, rest) = take(data); let (b, rest) = take(rest); let (c, _) = take(rest); - put_f64(out, a.mul_add(b, c)); + put_mul_add(out, a, b, c); return; } let mut state = take_bits(data).0 | 1; @@ -378,7 +379,13 @@ pub fn mul_adds(kind: u8, data: &[u8], out: &mut Vec) { (a, b, (p.next_up() - p) * 0.5) } }; - put_f64(out, a.mul_add(b, c)); + put_mul_add(out, a, b, c); + } +} + +fn put_mul_add(out: &mut Vec, a: f64, b: f64, c: f64) { + for x in [a, b, c, a.mul_add(b, c)] { + put_f64(out, x); } } diff --git a/ts/tests/fixtures.rs b/ts/tests/fixtures.rs index 2f0b2d825..69ba02ff2 100644 --- a/ts/tests/fixtures.rs +++ b/ts/tests/fixtures.rs @@ -364,9 +364,10 @@ fn lang() { } fn lang_differential(out: &Path) { - // Hand-written callers, type-checked against the output they call: one runs it, and - // one mixes integer types, which `tsc` must refuse. - for file in ["caller.ts", "brands.ts"] { + // Hand-written callers, type-checked against the output they call: one runs it, with + // the one difference from native it accepts, and one mixes integer types, which `tsc` + // must refuse. + for file in ["caller.ts", "fma.ts", "brands.ts"] { std::fs::copy(manifest_dir().join("fixtures/lang").join(file), out.join(file)).unwrap(); } typecheck(out); diff --git a/ts/tests/fma.ts b/ts/tests/fma.ts new file mode 100644 index 000000000..ff819b563 --- /dev/null +++ b/ts/tests/fma.ts @@ -0,0 +1,88 @@ +// The lang fixture's `mul_add` allowance: its exact rounding agrees with the runtime's, +// and it accepts native a last place off only where the translation is exact. +// +// usage: node fma.ts + +import { strict as assert } from "node:assert"; + +import { exactMulAdd, mulAddsWithin } from "../fixtures/lang/fma.ts"; +import * as rt from "../runtime/rt.ts"; + +function double(bits: bigint): number { + const view = new DataView(new ArrayBuffer(8)); + view.setBigUint64(0, bits); + return view.getFloat64(0); +} + +function bits(x: number): bigint { + const view = new DataView(new ArrayBuffer(8)); + view.setFloat64(0, x); + return view.getBigUint64(0); +} + +/** `mul_adds`' output for one set: its operands, then `result`. */ +function set(a: number, b: number, c: number, result: number): Uint8Array { + const out = new Uint8Array(32); + const view = new DataView(out.buffer); + [a, b, c, result].forEach((x, i) => view.setFloat64(i * 8, x)); + return out; +} + +/** The double `n` places above `x`, below if negative. */ +const step = (x: number, n: bigint) => double(bits(x) + n); + +// Exact rounding at the edges: a tie broken by the product's error alone, an overflow the +// sum brings back, subnormal results, and signed zeros. +const eps = 2 ** -52; +assert.equal(exactMulAdd(1 + eps, 1 + eps, -1), 2 * eps + eps * eps); +assert.equal(exactMulAdd(1 + 2 ** -27, 1 - 2 ** -27, -1), -(2 ** -54)); +assert.equal(exactMulAdd(eps / 2 + (eps * eps) / 2, 1 - eps / 2, 1), 1 + eps); +assert.equal(exactMulAdd(Number.MAX_VALUE, 2, -Number.MAX_VALUE), Number.MAX_VALUE); +assert.equal(exactMulAdd(Number.MAX_VALUE, 1 + eps, 0), Infinity); +assert.equal(exactMulAdd(5e-324, 0.5, 5e-324), 1e-323); +assert.equal(exactMulAdd(5e-324, 1.5, 0), 1e-323); +assert.ok(Object.is(exactMulAdd(-0, 1, -0), -0)); +assert.ok(Object.is(exactMulAdd(0, -1, 0), 0)); +assert.ok(Object.is(exactMulAdd(1, -1, 1), 0)); +assert.equal(exactMulAdd(Infinity, 1, 1), undefined); + +// It agrees with the runtime's `f64MulAdd` over operands of any bits, and near-cancelling ones. +let state = 0x9e3779b97f4a7c15n; +function next(): bigint { + state ^= (state << 13n) & 0xffffffffffffffffn; + state ^= state >> 7n; + state ^= (state << 17n) & 0xffffffffffffffffn; + return state; +} +for (let i = 0; i < 20000; i++) { + const [a, b] = [double(next()), double(next())]; + const c = i % 2 === 0 ? double(next()) : -(a * b); + const exact = exactMulAdd(a, b, c); + if (exact === undefined) continue; + assert.ok(Object.is(exact, rt.f64MulAdd(a, b, c)), `${a}, ${b}, ${c}`); +} + +// A product of half `c`'s last place that only its error moves off the tie, which wasm32's +// `fma` rounds to `c` (rust-lang/compiler-builtins#1353). +const [a, b, c] = [double(0xbe8c27a8edc66e22n), double(0xbd522f6292409f88n), double(0x3f41e8176d8609aan)]; +const exact = exactMulAdd(a, b, c) as number; +assert.equal(bits(exact), 0x3f41e8176d8609abn); +const right = set(a, b, c, exact); +assert.ok(mulAddsWithin(set(a, b, c, c), right), "native's own result"); +assert.ok(mulAddsWithin(set(a, b, c, step(exact, 1n)), right), "native a place above"); +assert.ok(mulAddsWithin(set(a, b, c, step(exact, -1n)), right), "native a place below"); +assert.ok(mulAddsWithin(right, right)); +// Native two places off. +assert.ok(!mulAddsWithin(set(a, b, c, step(exact, 2n)), right)); +// The translation is the side a place off. +assert.ok(!mulAddsWithin(right, set(a, b, c, step(exact, 1n)))); +// Both off, a place apart. +assert.ok(!mulAddsWithin(set(a, b, c, step(exact, 1n)), set(a, b, c, step(exact, 2n)))); +// Operands that differ. +assert.ok(!mulAddsWithin(set(a, b, c, step(exact, 1n)), set(a, b, step(c, 1n), exact))); +// A set past the first, and outputs of different lengths. +assert.ok(!mulAddsWithin(new Uint8Array([...right, ...set(a, b, c, step(exact, 2n))]), new Uint8Array([...right, ...right]))); +assert.ok(!mulAddsWithin(set(a, b, c, step(exact, 1n)), new Uint8Array([...right, ...right]))); +// Native infinite beside the largest double. +const max = Number.MAX_VALUE; +assert.ok(!mulAddsWithin(set(max, 1, 0, Infinity), set(max, 1, 0, max))); diff --git a/ts/tests/runtime.rs b/ts/tests/runtime.rs index 506da90ee..93f357965 100644 --- a/ts/tests/runtime.rs +++ b/ts/tests/runtime.rs @@ -25,6 +25,18 @@ fn nan_payloads() { assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr)); } +/// The lang fixture's `mul_add` allowance rounds exactly and accepts native a last place +/// off only where the translation is exact, which `fma.ts` checks. +#[test] +fn fma_allowance() { + let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests").join("fma.ts"); + let output = Command::new("node") + .arg(script) + .output() + .expect("running node, which the dev shell provides"); + assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr)); +} + /// A recursive `OnceLock` init, or `LazyLock` or `LazyCell` force, panics with std's /// message, which `once.ts` checks. #[test] From ca5ef088d2aae39bb4e078078208ac60aa8640c3 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 07:50:56 -0800 Subject: [PATCH 21/34] fix(ts): refuse offset_from across array fields, fail on dangling elements, keep null array casts null Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-unsafe.md | 4 ++ ts/README.md | 2 +- ts/fixtures/lang/expected/elements.ts | 7 ++- ts/fixtures/lang/src/elements.rs | 16 +++++- ts/fixtures/reject/expected.txt | 56 +++++++++---------- ts/fixtures/reject/src/lib.rs | 22 ++++++-- ts/runtime/rt.ts | 16 ++++-- ts/src/lower/mir/origin.rs | 78 ++++++++++++++++++--------- ts/tests/pointers.ts | 11 ++++ 9 files changed, 148 insertions(+), 64 deletions(-) diff --git a/quest/m3/ts-unsafe.md b/quest/m3/ts-unsafe.md index 722d4fa56..ee40e2b2c 100644 --- a/quest/m3/ts-unsafe.md +++ b/quest/m3/ts-unsafe.md @@ -44,6 +44,10 @@ none of its own `unsafe`. - `&raw const *r` where `r` is a `&T` to a value JS copies or a library object, such as foldhash's seed and arrayvec's `as_ptr`: the reference is the value, not where it is kept, so it needs the place the reference came from. +- A `Vec`'s spare capacity written through `as_mut_ptr` and committed by `set_len`: + `set_len` is refused, and a write past the length fails loudly at run time, since the + JS array would grow by it. The spare slots need to live outside the array's length + until `set_len` takes them. - `UnsafeCell::raw_get`, `from_mut`, and `replace`, refused with their span: none is in moq's census at the pin. - A slice's pointer cast to an array's of its length, `*(s as *const [u8] as *const diff --git a/ts/README.md b/ts/README.md index fe4fbf61c..9a4ddd6b3 100644 --- a/ts/README.md +++ b/ts/README.md @@ -835,7 +835,7 @@ they need. ## Not yet -Byte reinterpretation, pointer addresses, pointer arithmetic outside an array of values JS copies, raw allocation, and unions whose +Byte reinterpretation, pointer addresses, pointer arithmetic outside an array of values JS copies (other than wrapping arithmetic on a null or dangling pointer), raw allocation, and unions whose fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than `&dyn Debug`, `&dyn Display`, `dyn Future`, `dyn Any`, `dyn Fn*`, and the translated crates' traits' behind `&`, `&mut`, `Box`, or `Arc` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `dyn Error`, binds to diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index 2388fd6b2..bca67c3e2 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -204,8 +204,8 @@ export function inline(data: rt.ByteSlice, out: rt.ByteVec): void { * Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as * the array of one Rust treats it as, and into a mutable part of an array, measured from * the array, the pointer an empty slice keeps, within a shared part, which JS copies, - * memory nothing wrote copied, null and dangling pointers moved by address, and the crate's - * functions named as std's. + * memory nothing wrote copied, null and dangling pointers moved by address and cast from + * pointers to arrays, and the crate's functions named as std's. */ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { let _189!: rt.Result<[rt.u32, rt.u32], rt.u8>; @@ -293,6 +293,9 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecPush(out, rt.ptrWrappingAdd(dangling, 4294967295, 2, 2) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(dangling, -((n as rt.usize)), 2, 2), n as rt.usize, 2, 2) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.DANGLING, rt.isizeSub(rt.u8Rem(n, 3) as rt.isize, 1), 8, 8) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.sliceAsPtr(rt.NULL) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.byteSliceAsPtr(rt.NULL), n as rt.usize, 1, 1) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr(rt.DANGLING), -1, 2, 2) === rt.NULL ? 1 : 0); } } diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index d90a8adb5..24b897802 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -255,8 +255,8 @@ pub fn inline(data: &[u8], out: &mut Vec) { /// Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as /// the array of one Rust treats it as, and into a mutable part of an array, measured from /// the array, the pointer an empty slice keeps, within a shared part, which JS copies, -/// memory nothing wrote copied, null and dangling pointers moved by address, and the crate's -/// functions named as std's. +/// memory nothing wrote copied, null and dangling pointers moved by address and cast from +/// pointers to arrays, and the crate's functions named as std's. pub fn edges(data: &[u8], out: &mut Vec) { for &n in data { let bytes = [n, 1, 2, 3]; @@ -352,6 +352,18 @@ pub fn edges(data: &[u8], out: &mut Vec) { .wrapping_offset(isize::from(n % 3) - 1) .is_null() as u8, ); + // Null and dangling pointers to arrays, cast to ones to their first elements. + out.push((ptr::null::<[u32; 2]>() as *const u32).is_null() as u8); + out.push( + (ptr::null_mut::<[u8; 4]>() as *mut u8) + .wrapping_add(usize::from(n)) + .is_null() as u8, + ); + out.push( + (NonNull::<[u16; 3]>::dangling().as_ptr() as *mut u16) + .wrapping_sub(1) + .is_null() as u8, + ); } } diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index edc23b3a1..5ae4c09e8 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -206,29 +206,31 @@ a pointer into an array of `(u8, u8)`, which JS keeps as an object that knows no `offset_from` of pointers to `()`, which has no bytes at fixtures/reject/src/lib.rs:2172:13 a slice of `()`, which has no bytes, from a pointer at fixtures/reject/src/lib.rs:2172:39 a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2177:13 -a raw pointer to `[u32; 3]` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2188:10 -a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2190:10 -`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 at fixtures/reject/src/lib.rs:2213:5 -`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 at fixtures/reject/src/lib.rs:2214:26 -`offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays at fixtures/reject/src/lib.rs:2215:20 -`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 at fixtures/reject/src/lib.rs:2216:21 -a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2222:12 -a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2223:19 -a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2234:3 -a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2236:18 -a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2237:6 -a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2238:27 -a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2239:21 -a slice of one byte's place, which a byte slice can view only in an array of bytes at fixtures/reject/src/lib.rs:2246:2 -a slice or a cell of a mutable reference to `Reused` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2252:2 -`Clone::clone_from` of `std::vec::Vec`, whose `clone_from` is its own, which is not supported yet at fixtures/reject/src/lib.rs:2257:4 -unsupported call to `std::mem::size_of_val::` at fixtures/reject/src/lib.rs:2262:2 -a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2268:10 -a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2276:10 -a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2284:10 -a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2294:3 -an assignment through a reference to `std::sync::LazyLock` at fixtures/reject/src/lib.rs:2299:2 -a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2305:2 +a raw pointer to `[u32; 3]` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2190:10 +a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2192:10 +`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 at fixtures/reject/src/lib.rs:2225:5 +`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 at fixtures/reject/src/lib.rs:2226:26 +`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 at fixtures/reject/src/lib.rs:2227: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 at fixtures/reject/src/lib.rs:2228:23 +`offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays at fixtures/reject/src/lib.rs:2229:20 +`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 at fixtures/reject/src/lib.rs:2230:21 +a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2236:12 +a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2237:19 +a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2248:3 +a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2250:18 +a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2251:6 +a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2252:27 +a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2253:21 +a slice of one byte's place, which a byte slice can view only in an array of bytes at fixtures/reject/src/lib.rs:2260:2 +a slice or a cell of a mutable reference to `Reused` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2266:2 +`Clone::clone_from` of `std::vec::Vec`, whose `clone_from` is its own, which is not supported yet at fixtures/reject/src/lib.rs:2271:4 +unsupported call to `std::mem::size_of_val::` at fixtures/reject/src/lib.rs:2276:2 +a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2282:10 +a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2290:10 +a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2298:10 +a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2308:3 +an assignment through a reference to `std::sync::LazyLock` at fixtures/reject/src/lib.rs:2313:2 +a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2319:2 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:125:2 an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet at fixtures/reject/src/lib.rs:145:1 overriding `Iterator::size_hint`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:173:2 @@ -245,7 +247,7 @@ a raw pointer to `std::cell::UnsafeCell` in `InField`, which the crate may a raw pointer to `std::cell::UnsafeCell` in `InGeneric`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:1322:9 a raw pointer to `std::cell::UnsafeCell` in `InWritten`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:1326:9 a reference to `std::option::Option>`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there at fixtures/reject/src/lib.rs:2108:13 -a raw pointer to `(u8, u8)` in `([Cloned; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2313:23 -a raw pointer to `(u8, u8)` in `(Dropped, (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2313:23 -a raw pointer to `(u8, u8)` in `([Greatest; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2369:13 -a raw pointer to `(u8, u8)` in `([Touched; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2313:23 +a raw pointer to `(u8, u8)` in `([Cloned; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2327:23 +a raw pointer to `(u8, u8)` in `(Dropped, (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2327:23 +a raw pointer to `(u8, u8)` in `([Greatest; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2383:13 +a raw pointer to `(u8, u8)` in `([Touched; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2327:23 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index 223fb3bd8..18b737996 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -2177,7 +2177,9 @@ pub fn element_to_array(v: &[u32]) -> u32 { unsafe { (*(v.as_ptr() as *const [u32; 2]))[1] } } +/// A struct holding an array. pub struct Lanes { + /// The array a pointer points into. pub lanes: [u32; 3], } @@ -2198,13 +2200,23 @@ pub struct Halves { pub high: u32, } +/// Two arrays in one allocation, which JS keeps apart. +#[repr(C)] +pub struct Rows { + /// The first. + pub top: [u32; 2], + /// The second. + pub bottom: [u32; 2], +} + /// `offset_from` of pointers the body shows JS keeps in different arrays: two made apart -/// of one variable, two of a struct's fields, one into a shared subslice, a copy, against -/// one into what it was taken of, and one into a shared slice of a byte, also a copy, -/// against the byte's. -pub fn pointers_apart(n: u32, h: Halves, byte: u8) -> isize { +/// of one variable, two of a struct's fields, into two of its array fields, into two rows +/// of a nested array, one into a shared subslice, a copy, against one into what it was +/// taken of, and one into a shared slice of a byte, also a copy, against the byte's. +pub fn pointers_apart(n: u32, h: Halves, r: Rows, byte: u8) -> isize { let a = &raw const n; let b = &raw const n; + let grid = [[n; 2]; 2]; let lanes = [n; 4]; let part = &lanes[1..3]; let at = &raw const byte; @@ -2212,6 +2224,8 @@ pub fn pointers_apart(n: u32, h: Halves, byte: u8) -> isize { let bytes = std::slice::from_raw_parts(at, 1); a.offset_from(b) + (&raw const h.high).offset_from(&raw const h.low) + + r.bottom.as_ptr().offset_from(r.top.as_ptr()) + + grid[1].as_ptr().offset_from(grid[0].as_ptr()) + part.as_ptr().offset_from(lanes.as_ptr()) + bytes.as_ptr().offset_from(at) } diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 689ed1dc0..acfd8ab41 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -156,14 +156,21 @@ export type VecOf = T[] | ByteVec; /** A generic type's `Cow<[T]>`: the `Cow`, or the `ByteCow` of a `Cow<[u8]>`. */ export type CowOf = Cow | ByteCow; -/** The pointer to element `index` of `s`, a slice, a `[T; N]`, or a `Vec`'s storage. */ +/** + * The pointer to element `index` of `s`, a slice, a `[T; N]`, or a `Vec`'s storage. A null + * or dangling pointer to an array is one to its first element, at the same address. + */ export function sliceAsPtr(s: { [i: number]: T }, index = 0): Place { + if (s === NULL) return NULL; + if (s === DANGLING) return DANGLING; const view = (s as { [VIEW]?: View })[VIEW]; return view ? new AtIndex(view.s, view.start + index) : new AtIndex(s, index); } -/** The pointer to byte `index` of `s`. */ +/** The pointer to byte `index` of `s`, or `s` if it is null or dangling, as `sliceAsPtr`'s. */ export function byteSliceAsPtr(s: ByteSlice, index = 0): Place { + if (s === NULL) return NULL; + if (s === DANGLING) return DANGLING; return new AtIndex(s.data, s.start + index); } @@ -226,11 +233,12 @@ class Lone implements ProxyHandler { /** * Where `p` points, as an element of an array: its own, or for a pointer to a value in no - * array, element 0 of an array of one that reads and writes it, as Rust treats one. + * array, element 0 of an array of one that reads and writes it, as Rust treats one. Null + * and dangling pointers point into an array of none, so any access through them fails. */ function elemOf(p: Place): AtIndex { if (p instanceof AtIndex) return p; - if (p === NULL) return new AtIndex([] as T[], 0); + if (p === NULL || p === DANGLING) return new AtIndex([] as T[], 0); let lone = LONE.get(p) as T[] | undefined; if (!lone) { lone = new Proxy(new Array(1), new Lone(p)); diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index c76ecfca4..0037ae0a7 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -15,23 +15,23 @@ use crate::lower::pointer::Pointee; /// The array JS keeps what a pointer points at in. #[derive(Clone, Copy, PartialEq, Eq)] -enum Array { +enum Array<'tcx> { /// An array of one, around the place a `&raw` here took, which each `&raw` makes anew. Lone(Location), /// A shared slice JS copies, of values it copies or of a byte in no array, made by the /// call ending this block, with its length if the body fixes it. Copy(BasicBlock, Option), - /// The array of a variable of this body's own, or of a field of one. - Owned, + /// The array of a variable of this body's own, or of a field of one, at this place. + Owned(mir::Place<'tcx>), } -impl Array { +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::Copy(_, len) => len, - Array::Owned => None, + Array::Owned(_) => None, } } @@ -40,15 +40,15 @@ impl Array { match self { Array::Lone(_) => "a value in no array", Array::Copy(..) => "a shared slice JS copies", - Array::Owned => "an array of this body's own", + Array::Owned(_) => "an array of this body's own", } } } /// Where a pointer points: into which array, at which index if the body fixes it. #[derive(Clone, Copy)] -struct Origin { - array: Array, +struct Origin<'tcx> { + array: Array<'tcx>, index: Option, } @@ -72,7 +72,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { if self.b.t.zero_sized(pointee) || self.b.t.pointee(pointee) != Pointee::Place { return None; } - let out = |o: Origin, n: Option, what: &str| { + let out = |o: Origin<'tcx>, n: Option, what: &str| { let index = o.index?; let past = o.array.len().is_some_and(|len| index + n.unwrap_or(0) > len); (index < 0 || past).then(|| { @@ -85,11 +85,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { match (owner.as_str(), name.as_str()) { ("ptr" | "NonNull", "offset_from" | "offset_from_unsigned") => { let (a, b) = (self.origin(arg(0)?)?, self.origin(arg(1)?)?); - let apart = match (a.array, b.array) { - (Array::Owned, Array::Owned) => false, - (a, b) => a != b, - }; - apart.then(|| { + self.apart(a.array, b.array).then(|| { format!( "`offset_from` between a pointer into {} and one into {}, which JS keeps in different arrays", a.array.what(), @@ -130,8 +126,38 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } } + /// Whether JS keeps `a` and `b` in different arrays. Arrays of different variables are; + /// of one variable's, those at different fields or constant indices are, where Rust's + /// are one allocation. + fn apart(&self, a: Array<'tcx>, b: Array<'tcx>) -> bool { + let (Array::Owned(a), Array::Owned(b)) = (a, b) else { + return a != b; + }; + let index = |elem: ProjectionElem>| match elem { + ProjectionElem::ConstantIndex { + offset, + from_end: false, + .. + } => Some(i128::from(offset)), + ProjectionElem::Index(local) => match self.def_rvalue(self.traced(&Operand::Copy(local.into()))?)? { + Rvalue::Use(op, _) => self.const_int(op), + _ => None, + }, + _ => None, + }; + a.local != b.local + || a.projection + .iter() + .zip(b.projection) + .find(|(x, y)| x != y) + .is_some_and(|pair| match pair { + (ProjectionElem::Field(x, _), ProjectionElem::Field(y, _)) => x != y, + (x, y) => index(x).zip(index(y)).is_some_and(|(x, y)| x != y), + }) + } + /// Where the pointer `op` points, if the body shows it. - fn origin(&self, op: &Operand<'tcx>) -> Option { + fn origin(&self, op: &Operand<'tcx>) -> Option> { let local = self.traced(op)?; if let Some((bb, i)) = self.def_at[local] { let rvalue = self.def_rvalue(local)?; @@ -148,12 +174,16 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { // `&raw mut *p` is `p`. [ProjectionElem::Deref] => self.origin(&Operand::Copy(mir::Place::from(place.local))), [.., ProjectionElem::Deref] => None, - [.., ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. }] => { - (!place.projection.contains(&ProjectionElem::Deref)).then_some(Origin { - array: Array::Owned, - index: None, - }) - } + [ + array @ .., + ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. }, + ] => (!place.projection.contains(&ProjectionElem::Deref)).then(|| Origin { + array: Array::Owned(mir::Place { + local: place.local, + projection: self.tcx.mk_place_elems(array), + }), + index: None, + }), _ => Some(Origin { array: Array::Lone(Location { block: bb, @@ -179,7 +209,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } /// `o` moved by the offset `op` as `add`, `sub`, or `offset` moves it. - fn moved(&self, o: Origin, name: &str, op: &Operand<'tcx>) -> Origin { + fn moved(&self, o: Origin<'tcx>, name: &str, op: &Operand<'tcx>) -> Origin<'tcx> { let n = self.const_int(op).map(|n| match name { "sub" => -n, _ => n, @@ -191,7 +221,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } /// Where the slice or `Vec`, or reference to one, `op` starts. - fn start(&self, op: &Operand<'tcx>) -> Option { + fn start(&self, op: &Operand<'tcx>) -> Option> { let local = self.traced(op)?; if self.def_at[local].is_some() { return match self.def_rvalue(local)? { @@ -207,7 +237,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } => { Some(Origin { - array: Array::Owned, + array: Array::Owned(*place), index: Some(0), }) } diff --git a/ts/tests/pointers.ts b/ts/tests/pointers.ts index 936343959..1f44ebf84 100644 --- a/ts/tests/pointers.ts +++ b/ts/tests/pointers.ts @@ -53,3 +53,14 @@ unsure(() => { rt.ptrAdd(rt.sliceAsPtr(vec), 1).v = 8; }); assert.equal(vec.length, 1); + +// A dangling pointer read through, written through, or copied from, which Rust leaves +// undefined, rather than reading `undefined`. +const dangling: rt.Place = rt.DANGLING; +unsure(() => rt.ptrAdd(dangling, 1).v); +unsure(() => { + rt.ptrAdd(dangling, 1).v = 1; +}); +unsure(() => rt.ptrCopy(dangling, rt.sliceAsPtr(vec), 1)); +unsure(() => rt.ptrSlice(dangling, 1)); +assert.equal(vec[0], 7); From a4b0bc371c731d4aa292d38174867ca45791c5a9 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 07:52:19 -0800 Subject: [PATCH 22/34] chore(ts): regenerate the reject census after merging main Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.md | 310 ++++++++++++++++++++++++----------- 1 file changed, 217 insertions(+), 93 deletions(-) diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 67e9a15d8..64dac4802 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,16 +1,16 @@ # Census of `reject-fixture` -- Refusals: 231 -- Function instances: 293 +- Refusals: 253 +- Function instances: 300 - Generic functions no instance reached, lowered over their type parameters: 13 ## By construct | Count | Construct | First | | ---: | --- | --- | -| 49 | a raw pointer to `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:608:10 | -| 25 | unsupported call to `_` | fixtures/reject/src/lib.rs:160:8 | -| 22 | a raw pointer to `_` in a variable assigned again while it may point there | fixtures/reject/src/lib.rs:616:10 | +| 50 | a raw pointer to `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:608:10 | +| 23 | a raw pointer to `_` in a variable assigned again while it may point there | fixtures/reject/src/lib.rs:616:10 | +| 21 | unsupported call to `_` | fixtures/reject/src/lib.rs:160:8 | | 15 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:1337:33 | | 15 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | fixtures/reject/src/lib.rs:1655:18 | | 5 | `_` with a mutable reference to a value JS copies, which its mapping would copy | fixtures/reject/src/lib.rs:1235:22 | @@ -18,16 +18,22 @@ | 4 | a variant of `_`, which is not supported: either is the same JS string | fixtures/reject/src/lib.rs:331:2 | | 4 | an assignment through a reference to `_` | fixtures/reject/src/lib.rs:83:2 | | 3 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | fixtures/reject/src/lib.rs:21:4 | +| 3 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:1710:3 | | 3 | a copy of a closure that changes what it captured | fixtures/reject/src/lib.rs:224:14 | -| 3 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | fixtures/reject/src/lib.rs:2164:2 | +| 3 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | fixtures/reject/src/lib.rs:2266:2 | +| 3 | an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:1713:56 | | 3 | replacing the value behind a reference to `_` | fixtures/reject/src/lib.rs:91:2 | | 3 | unsupported type `_` | fixtures/reject/src/lib.rs:7:14 | | 2 | `_` is not supported: its `None` and `Some` would both be `rt.None` | fixtures/reject/src/lib.rs:16:15 | | 2 | `_` of `_`, whose `None` is `rt.None`, which is not supported yet | fixtures/reject/src/lib.rs:37:4 | | 2 | `_`, a trait object of another crate's trait, which is not supported yet | fixtures/reject/src/lib.rs:11:16 | | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | fixtures/reject/src/lib.rs:464:23 | +| 2 | `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/lib.rs:2225:5 | +| 2 | `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/lib.rs:2227:24 | | 2 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:555:27 | | 2 | a coercion to `_`, which is not supported yet | fixtures/reject/src/lib.rs:455:40 | +| 2 | a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2250:18 | +| 2 | a raw or mutable slice of a byte in no array of bytes | fixtures/reject/src/lib.rs:2236:12 | | 2 | a raw pointer to a place of `()`, which has no address of its own | fixtures/reject/src/lib.rs:728:15 | | 2 | an iterator of the crate implementing `_`, which is not supported yet | fixtures/reject/src/lib.rs:145:1 | | 2 | comparing pointers to `_`, each a place JS makes of its own | fixtures/reject/src/lib.rs:596:2 | @@ -50,24 +56,34 @@ | 1 | `clone_from` of `_`, whose items' `clone_from` is their own, which is not supported yet | fixtures/reject/src/lib.rs:2118:4 | | 1 | `collect` of `_`, which sorts by `_`'s own `Ord`, of entries that drop, which is not supported yet | fixtures/reject/src/lib.rs:955:22 | | 1 | `fmt::Arguments` with a placeholder, or not made in place, written to `_`'s own `fmt::Write`, which is not supported yet: std splits what a placeholder writes into `write_str` calls the runtime does not make | fixtures/reject/src/lib.rs:55:10 | +| 1 | `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/lib.rs:2230:21 | +| 1 | `offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays | fixtures/reject/src/lib.rs:2229:20 | +| 1 | `offset_from` of pointers to `()`, which has no bytes | fixtures/reject/src/lib.rs:2172:13 | +| 1 | `offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2165:36 | | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | fixtures/reject/src/lib.rs:209:40 | | 1 | a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet | fixtures/reject/src/lib.rs:202:8 | | 1 | a cast from `_` to `_` | fixtures/reject/src/lib.rs:68:2 | -| 1 | a cast from `_` to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:1713:33 | | 1 | a coercion of `_` to `_`, a pointer to a slice | fixtures/reject/src/lib.rs:1714:28 | | 1 | a coercion to `_` | fixtures/reject/src/lib.rs:291:2 | | 1 | a constant of type `_`, a pointer into memory translation can't name | fixtures/reject/src/lib.rs:778:3 | +| 1 | a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2248:3 | | 1 | a dangling pointer to `_`, a value of no bytes made of more than structs and tuples | fixtures/reject/src/lib.rs:737:11 | | 1 | a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is | fixtures/reject/src/lib.rs:77:34 | | 1 | a mapped guard of `_`, which JS can't write in place | fixtures/reject/src/lib.rs:2152:10 | | 1 | a mutable reference into a `Result`'s payload, passed on or kept | fixtures/reject/src/lib.rs:362:12 | | 1 | a mutable static, which any code may write | fixtures/reject/src/lib.rs:61:11 | +| 1 | a pointer into `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2159:12 | +| 1 | a pointer into an array of `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2164:30 | +| 1 | a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2252:27 | | 1 | a pointer's address, which JS has no counterpart of | fixtures/reject/src/lib.rs:560:17 | | 1 | a printable type must implement `Display` or `Debug` | fixtures/reject/src/lib.rs:64:1 | | 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | fixtures/reject/src/lib.rs:577:10 | | 1 | a reference through which a raw pointer may be made into `_` in an element, a payload, or a union field, which is not supported yet | fixtures/reject/src/lib.rs:1364:11 | | 1 | a scalar constant of type `_` | fixtures/reject/src/lib.rs:8:2 | -| 1 | a slice of one byte's place, which a byte slice can view only in an array of bytes | fixtures/reject/src/lib.rs:2158:2 | +| 1 | a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2163:11 | +| 1 | a slice of `()`, which has no bytes, from a pointer | fixtures/reject/src/lib.rs:2172:39 | +| 1 | a slice of one byte's place, which a byte slice can view only in an array of bytes | fixtures/reject/src/lib.rs:2260:2 | +| 1 | a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2251:6 | | 1 | a trait object of `_`, which is not supported yet | fixtures/reject/src/lib.rs:481:26 | | 1 | an upcast from `_` to `_`, which is not supported yet | fixtures/reject/src/lib.rs:532:32 | | 1 | arithmetic on `_` | fixtures/reject/src/lib.rs:8:2 | @@ -91,25 +107,24 @@ The type or module each refused type or path belongs to. | ---: | --- | --- | | 39 | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:687:15 | | 27 | `(u32, u32)` | fixtures/reject/src/lib.rs:608:10 | -| 6 | `[u8; 4]` | fixtures/reject/src/lib.rs:481:26 | +| 11 | `(u8, u8)` | fixtures/reject/src/lib.rs:1272:31 | +| 9 | `[u8; 4]` | fixtures/reject/src/lib.rs:481:26 | +| 7 | `u32` | fixtures/reject/src/lib.rs:555:27 | | 6 | `f16` | fixtures/reject/src/lib.rs:7:14 | | 6 | `std::borrow::Cow` | fixtures/reject/src/lib.rs:331:2 | -| 6 | `u32` | fixtures/reject/src/lib.rs:555:27 | -| 5 | `(u8, u8)` | fixtures/reject/src/lib.rs:1272:31 | | 5 | `Gap` | fixtures/reject/src/lib.rs:908:10 | | 5 | `Option` | fixtures/reject/src/lib.rs:16:15 | -| 5 | `u8` | fixtures/reject/src/lib.rs:253:6 | | 4 | `Iterator` | fixtures/reject/src/lib.rs:37:4 | | 4 | `std::pin::Pin` | fixtures/reject/src/lib.rs:91:2 | | 4 | `std::str` | fixtures/reject/src/lib.rs:439:4 | +| 4 | `u8` | fixtures/reject/src/lib.rs:253:6 | | 4 | `{closure}` | fixtures/reject/src/lib.rs:319:13 | -| 3 | `*const [u8; 4]` | fixtures/reject/src/lib.rs:1713:33 | | 3 | `Mid` | fixtures/reject/src/lib.rs:1355:34 | | 3 | `Outer` | fixtures/reject/src/lib.rs:1355:34 | | 3 | `Reused` | fixtures/reject/src/lib.rs:21:4 | -| 3 | `std::ptr` | fixtures/reject/src/lib.rs:1710:3 | | 3 | `std::vec::Vec` | fixtures/reject/src/lib.rs:310:16 | | 2 | `(u16, u16)` | fixtures/reject/src/lib.rs:1176:31 | +| 2 | `*const [u8; 4]` | fixtures/reject/src/lib.rs:1714:28 | | 2 | `Counted` | fixtures/reject/src/lib.rs:389:2 | | 2 | `InArray` | fixtures/reject/src/lib.rs:1302:9 | | 2 | `InPayload` | fixtures/reject/src/lib.rs:1314:9 | @@ -118,6 +133,7 @@ The type or module each refused type or path belongs to. | 2 | `Inside` | fixtures/reject/src/lib.rs:1845:20 | | 2 | `Poll` | fixtures/reject/src/lib.rs:72:20 | | 2 | `Scaled` | fixtures/reject/src/lib.rs:464:23 | +| 2 | `[u32; 3]` | fixtures/reject/src/lib.rs:2190:10 | | 2 | `[u8; 2]` | fixtures/reject/src/lib.rs:1726:37 | | 2 | `[u8; 3]` | fixtures/reject/src/lib.rs:1726:15 | | 2 | `dyn Scaled` | fixtures/reject/src/lib.rs:464:23 | @@ -130,15 +146,14 @@ The type or module each refused type or path belongs to. | 2 | `std::mem::MaybeUninit` | fixtures/reject/src/lib.rs:922:19 | | 2 | `std::ops::Range` | fixtures/reject/src/lib.rs:160:8 | | 2 | `std::option::Option` | fixtures/reject/src/lib.rs:369:2 | -| 2 | `std::ptr::const_ptr` | fixtures/reject/src/lib.rs:1713:73 | | 2 | `std::std` | fixtures/reject/src/lib.rs:567:11 | | 2 | `std::sync::Arc` | fixtures/reject/src/lib.rs:291:2 | | 2 | `std::sync::MutexGuard` | fixtures/reject/src/lib.rs:83:2 | | 2 | `std::vec::IntoIter` | fixtures/reject/src/lib.rs:434:17 | -| 1 | `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2263:2 | -| 1 | `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2247:2 | -| 1 | `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2281:13 | -| 1 | `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2312:2 | +| 1 | `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2365:2 | +| 1 | `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2349:2 | +| 1 | `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2383:13 | +| 1 | `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2414:2 | | 1 | `(std::vec::Vec, Tens)` | fixtures/reject/src/lib.rs:1272:31 | | 1 | `(u16, ByKeyReplace)` | fixtures/reject/src/lib.rs:1621:55 | | 1 | `(u16, ByKeySet)` | fixtures/reject/src/lib.rs:1613:51 | @@ -147,7 +162,6 @@ The type or module each refused type or path belongs to. | 1 | `(u16, ByRefTake)` | fixtures/reject/src/lib.rs:1605:32 | | 1 | `(u16, InTuple)` | fixtures/reject/src/lib.rs:1346:33 | | 1 | `*const [u8]` | fixtures/reject/src/lib.rs:1714:28 | -| 1 | `*const u8` | fixtures/reject/src/lib.rs:1713:33 | | 1 | `*mut [u8; 4]` | fixtures/reject/src/lib.rs:1712:41 | | 1 | `Aliased` | fixtures/reject/src/lib.rs:1120:29 | | 1 | `Assigned` | fixtures/reject/src/lib.rs:2012:20 | @@ -174,7 +188,7 @@ The type or module each refused type or path belongs to. | 1 | `BytesMut` | fixtures/reject/src/lib.rs:383:6 | | 1 | `Called` | fixtures/reject/src/lib.rs:1086:28 | | 1 | `Chosen` | fixtures/reject/src/lib.rs:1906:17 | -| 1 | `Clone` | fixtures/reject/src/lib.rs:2169:4 | +| 1 | `Clone` | fixtures/reject/src/lib.rs:2271:4 | | 1 | `Closed` | fixtures/reject/src/lib.rs:2078:15 | | 1 | `Compared` | fixtures/reject/src/lib.rs:1873:22 | | 1 | `Copied` | fixtures/reject/src/lib.rs:1176:31 | @@ -194,6 +208,7 @@ The type or module each refused type or path belongs to. | 1 | `InGeneric` | fixtures/reject/src/lib.rs:1322:9 | | 1 | `InNested` | fixtures/reject/src/lib.rs:1310:9 | | 1 | `Lane` | fixtures/reject/src/lib.rs:872:19 | +| 1 | `Lanes` | fixtures/reject/src/lib.rs:2192:10 | | 1 | `Loaded` | fixtures/reject/src/lib.rs:1142:29 | | 1 | `Looped` | fixtures/reject/src/lib.rs:1110:26 | | 1 | `Mapped` | fixtures/reject/src/lib.rs:1187:39 | @@ -226,31 +241,33 @@ The type or module each refused type or path belongs to. | 1 | `[InArray; 2]` | fixtures/reject/src/lib.rs:1337:33 | | 1 | `[InWritten; 1]` | fixtures/reject/src/lib.rs:1398:35 | | 1 | `[std::cell::UnsafeCell; 2]` | fixtures/reject/src/lib.rs:2091:20 | +| 1 | `[u32; 2]` | fixtures/reject/src/lib.rs:2177:13 | | 1 | `dyn Keyed` | fixtures/reject/src/lib.rs:532:32 | | 1 | `poll` | fixtures/reject/src/lib.rs:502:2 | | 1 | `rt.Poll` | fixtures/reject/src/lib.rs:502:2 | | 1 | `std::any::Any` | fixtures/reject/src/lib.rs:538:4 | | 1 | `std::cell::Cell` | fixtures/reject/src/lib.rs:955:22 | -| 1 | `std::cell::LazyCell` | fixtures/reject/src/lib.rs:2206:3 | +| 1 | `std::cell::LazyCell` | fixtures/reject/src/lib.rs:2308:3 | | 1 | `std::collections::BTreeMap` | fixtures/reject/src/lib.rs:955:22 | | 1 | `std::collections::HashMap` | fixtures/reject/src/lib.rs:253:6 | | 1 | `std::default::Default` | fixtures/reject/src/lib.rs:2119:2 | | 1 | `std::iter::Map` | fixtures/reject/src/lib.rs:209:40 | | 1 | `std::iter::Zip` | fixtures/reject/src/lib.rs:202:8 | | 1 | `std::iter` | fixtures/reject/src/lib.rs:1235:22 | -| 1 | `std::mem` | fixtures/reject/src/lib.rs:2174:2 | +| 1 | `std::mem` | fixtures/reject/src/lib.rs:2276:2 | | 1 | `std::net::AddrParseError` | fixtures/reject/src/lib.rs:438:32 | | 1 | `std::net::Ipv4Addr` | fixtures/reject/src/lib.rs:438:32 | | 1 | `std::num` | fixtures/reject/src/lib.rs:1277:4 | | 1 | `std::ops::RangeFrom` | fixtures/reject/src/lib.rs:984:32 | | 1 | `std::ops::RangeTo` | fixtures/reject/src/lib.rs:984:11 | -| 1 | `std::ptr::mut_ptr` | fixtures/reject/src/lib.rs:1713:56 | +| 1 | `std::ptr::const_ptr` | fixtures/reject/src/lib.rs:1717:58 | +| 1 | `std::ptr` | fixtures/reject/src/lib.rs:1712:14 | | 1 | `std::slice::IterMut` | fixtures/reject/src/lib.rs:766:15 | | 1 | `std::std::Layout` | fixtures/reject/src/lib.rs:565:15 | -| 1 | `std::sync::LazyLock` | fixtures/reject/src/lib.rs:2211:2 | +| 1 | `std::sync::LazyLock` | fixtures/reject/src/lib.rs:2313:2 | | 1 | `std::sync::Weak` | fixtures/reject/src/lib.rs:455:40 | | 1 | `std::task::Poll` | fixtures/reject/src/lib.rs:77:34 | -| 1 | `std::thread::LocalKey` | fixtures/reject/src/lib.rs:2217:2 | +| 1 | `std::thread::LocalKey` | fixtures/reject/src/lib.rs:2319:2 | | 1 | `str` | fixtures/reject/src/lib.rs:326:4 | | 1 | `u64` | fixtures/reject/src/lib.rs:68:2 | | 1 | `value` | fixtures/reject/src/lib.rs:31:2 | @@ -268,6 +285,7 @@ The type or module each refused type or path belongs to. | 2 | `_` of `_`, whose `None` is `rt.None`, which is not supported yet | `Iterator::scan`, `Option` | fixtures/reject/src/lib.rs:37:4 | | 2 | `_`, a trait object of another crate's trait, which is not supported yet | `dyn std::iter::Iterator` | fixtures/reject/src/lib.rs:11:16 | | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | `dyn Scaled`, `Scaled` | fixtures/reject/src/lib.rs:464:23 | +| 2 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | `[u8; 4]` | fixtures/reject/src/lib.rs:1710:3 | | 2 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::mem::MaybeUninit`, `Gap` | fixtures/reject/src/lib.rs:922:19 | | 2 | a raw pointer to `_` in a variable assigned again while it may point there | `[u8; 4]` | fixtures/reject/src/lib.rs:1733:26 | | 2 | a raw pointer to `_` in a variable assigned again while it may point there | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:696:15 | @@ -276,6 +294,7 @@ The type or module each refused type or path belongs to. | 2 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `Inside` | fixtures/reject/src/lib.rs:1845:20 | | 2 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `[u8; 4]` | fixtures/reject/src/lib.rs:1733:26 | | 2 | an assignment through a reference to `_` | `std::pin::Pin` | fixtures/reject/src/lib.rs:107:2 | +| 2 | an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in | `[u8; 4]` | fixtures/reject/src/lib.rs:1713:56 | | 2 | comparing pointers to `_`, each a place JS makes of its own | `u32` | fixtures/reject/src/lib.rs:596:2 | | 2 | replacing the value behind a reference to `_` | `std::pin::Pin` | fixtures/reject/src/lib.rs:91:2 | | 2 | unsupported call to `_` | `::sum` | fixtures/reject/src/lib.rs:434:17 | @@ -289,7 +308,7 @@ The type or module each refused type or path belongs to. | 1 | `_` is not supported: its field `_` is `_`, typed `_`, whose `Pending` would be `rt.OuterPending` as `_` | `Waiting`, `poll`, `Poll`, `rt.Poll`, `Poll` | fixtures/reject/src/lib.rs:502:2 | | 1 | `_` of `_` by `_`, which is not supported yet | `HashMap::contains_key`, `std::collections::HashMap`, `u8` | fixtures/reject/src/lib.rs:253:6 | | 1 | `_` of `_`, which is not supported yet | `std::any::Any::type_id`, `dyn std::any::Any` | fixtures/reject/src/lib.rs:538:4 | -| 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `Clone::clone_from`, `std::vec::Vec` | fixtures/reject/src/lib.rs:2169:4 | +| 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `Clone::clone_from`, `std::vec::Vec` | fixtures/reject/src/lib.rs:2271:4 | | 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `slice::clone_from_slice`, `Reused` | fixtures/reject/src/lib.rs:286:4 | | 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `slice::fill`, `Reused` | fixtures/reject/src/lib.rs:21:4 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::collections::LinkedList::retain` | fixtures/reject/src/lib.rs:2117:4 | @@ -302,20 +321,23 @@ The type or module each refused type or path belongs to. | 1 | `clone_from` of `_`, whose items' `clone_from` is their own, which is not supported yet | `std::collections::LinkedList` | fixtures/reject/src/lib.rs:2118:4 | | 1 | `collect` of `_`, which sorts by `_`'s own `Ord`, of entries that drop, which is not supported yet | `std::collections::BTreeMap`, `std::cell::Cell` | fixtures/reject/src/lib.rs:955:22 | | 1 | `fmt::Arguments` with a placeholder, or not made in place, written to `_`'s own `fmt::Write`, which is not supported yet: std splits what a placeholder writes into `write_str` calls the runtime does not make | `Sink` | fixtures/reject/src/lib.rs:55:10 | +| 1 | `offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2165:36 | | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | `std::iter::Map` | fixtures/reject/src/lib.rs:209:40 | | 1 | a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet | `std::iter::Zip` | fixtures/reject/src/lib.rs:202:8 | | 1 | a cast from `_` to `_` | `u64`, `f16` | fixtures/reject/src/lib.rs:68:2 | -| 1 | a cast from `_` to `_`, which reads the bytes of one type as another | `*const [u8; 4]`, `*const u8` | fixtures/reject/src/lib.rs:1713:33 | -| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `[u8; 4]`, `u8` | fixtures/reject/src/lib.rs:1713:17 | +| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `[u32; 2]` | fixtures/reject/src/lib.rs:2177:13 | | 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `u8` | fixtures/reject/src/lib.rs:555:27 | | 1 | a coercion of `_` to `_`, a pointer to a slice | `*const [u8; 4]`, `*const [u8]` | fixtures/reject/src/lib.rs:1714:28 | | 1 | a coercion to `_` | `std::sync::Arc` | fixtures/reject/src/lib.rs:291:2 | | 1 | a coercion to `_`, which is not supported yet | `&Tail` | fixtures/reject/src/lib.rs:491:30 | | 1 | a coercion to `_`, which is not supported yet | `std::sync::Weak` | fixtures/reject/src/lib.rs:455:40 | | 1 | a constant of type `_`, a pointer into memory translation can't name | `&u32` | fixtures/reject/src/lib.rs:778:3 | +| 1 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2162:3 | | 1 | a dangling pointer to `_`, a value of no bytes made of more than structs and tuples | `Only` | fixtures/reject/src/lib.rs:737:11 | | 1 | a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is | `std::task::Poll` | fixtures/reject/src/lib.rs:77:34 | | 1 | a mapped guard of `_`, which JS can't write in place | `{closure}` | fixtures/reject/src/lib.rs:2152:10 | +| 1 | a pointer into `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2159:12 | +| 1 | a pointer into an array of `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2164:30 | | 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | `std::vec::Vec` | fixtures/reject/src/lib.rs:577:10 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u16, u16)`, `Copied` | fixtures/reject/src/lib.rs:1176:31 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u16, u16)`, `Pushed` | fixtures/reject/src/lib.rs:1248:22 | @@ -325,15 +347,16 @@ The type or module each refused type or path belongs to. | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u32, u32)`, `Track` | fixtures/reject/src/lib.rs:852:19 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u32, u32)`, `Window` | fixtures/reject/src/lib.rs:669:2 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u32, u32)`, `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:687:15 | -| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2263:2 | -| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2247:2 | -| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2281:13 | -| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2312:2 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2365:2 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2349:2 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2383:13 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2414:2 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByKeyReplace`, `(u16, ByKeyReplace)` | fixtures/reject/src/lib.rs:1621:55 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByKeySet`, `(u16, ByKeySet)` | fixtures/reject/src/lib.rs:1613:51 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByKeyTake`, `(u16, ByKeyTake)` | fixtures/reject/src/lib.rs:1628:52 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByRefReplace`, `(u16, ByRefReplace)` | fixtures/reject/src/lib.rs:1597:32 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByRefTake`, `(u16, ByRefTake)` | fixtures/reject/src/lib.rs:1605:32 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `[u32; 3]`, `Lanes` | fixtures/reject/src/lib.rs:2192:10 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `Aliased` | fixtures/reject/src/lib.rs:1120:29 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `ByBox` | fixtures/reject/src/lib.rs:1504:35 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `ByClosure` | fixtures/reject/src/lib.rs:1519:35 | @@ -362,6 +385,7 @@ The type or module each refused type or path belongs to. | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `Repeated` | fixtures/reject/src/lib.rs:1221:18 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `Rewritten` | fixtures/reject/src/lib.rs:1130:47 | | 1 | a raw pointer to `_` in a variable assigned again while it may point there | `Target` | fixtures/reject/src/lib.rs:1669:10 | +| 1 | a raw pointer to `_` in a variable assigned again while it may point there | `[u32; 3]` | fixtures/reject/src/lib.rs:2190:10 | | 1 | a raw pointer to `_` in an element, a payload, or a union field, which is not supported yet | `(u32, u32)` | fixtures/reject/src/lib.rs:679:10 | | 1 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | `InArray`, `[InArray; 2]` | fixtures/reject/src/lib.rs:1337:33 | | 1 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | `InTuple`, `(u16, InTuple)` | fixtures/reject/src/lib.rs:1346:33 | @@ -388,14 +412,16 @@ The type or module each refused type or path belongs to. | 1 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:1655:18 | | 1 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `std::option::Option` | fixtures/reject/src/lib.rs:2108:13 | | 1 | a scalar constant of type `_` | `f16` | fixtures/reject/src/lib.rs:8:2 | -| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `Reused` | fixtures/reject/src/lib.rs:2164:2 | -| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `std::cell::LazyCell` | fixtures/reject/src/lib.rs:2206:3 | -| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `std::thread::LocalKey` | fixtures/reject/src/lib.rs:2217:2 | +| 1 | a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2163:11 | +| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `Reused` | fixtures/reject/src/lib.rs:2266:2 | +| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `std::cell::LazyCell` | fixtures/reject/src/lib.rs:2308:3 | +| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `std::thread::LocalKey` | fixtures/reject/src/lib.rs:2319:2 | | 1 | a trait object of `_`, which is not supported yet | `[u8; 4]` | fixtures/reject/src/lib.rs:481:26 | -| 1 | an assignment through a reference to `_` | `std::sync::LazyLock` | fixtures/reject/src/lib.rs:2211:2 | +| 1 | an assignment through a reference to `_` | `std::sync::LazyLock` | fixtures/reject/src/lib.rs:2313:2 | | 1 | an assignment through a reference to `_` | `std::sync::MutexGuard` | fixtures/reject/src/lib.rs:83:2 | | 1 | an iterator of the crate implementing `_`, which is not supported yet | `DoubleEndedIterator` | fixtures/reject/src/lib.rs:145:1 | | 1 | an iterator of the crate implementing `_`, which is not supported yet | `FusedIterator` | fixtures/reject/src/lib.rs:193:1 | +| 1 | an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2161:13 | | 1 | an upcast from `_` to `_`, which is not supported yet | `dyn Keyed`, `dyn std::any::Any` | fixtures/reject/src/lib.rs:532:32 | | 1 | arithmetic on `_` | `f16` | fixtures/reject/src/lib.rs:8:2 | | 1 | comparing `_` | `*mut [u8; 4]` | fixtures/reject/src/lib.rs:1712:41 | @@ -421,17 +447,13 @@ The type or module each refused type or path belongs to. | 1 | unsupported call to `_` | `::hash` | fixtures/reject/src/lib.rs:301:10 | | 1 | unsupported call to `_` | `std::any::TypeId::of` | fixtures/reject/src/lib.rs:538:17 | | 1 | unsupported call to `_` | `std::borrow::Cow::is_borrowed` | fixtures/reject/src/lib.rs:343:2 | -| 1 | unsupported call to `_` | `std::mem::size_of_val` | fixtures/reject/src/lib.rs:2174:2 | +| 1 | unsupported call to `_` | `std::mem::size_of_val` | fixtures/reject/src/lib.rs:2276:2 | | 1 | unsupported call to `_` | `std::num::is_ascii_octdigit` | fixtures/reject/src/lib.rs:1277:4 | | 1 | unsupported call to `_` | `std::ops::Range::contains` | fixtures/reject/src/lib.rs:160:8 | | 1 | unsupported call to `_` | `std::ops::Range::is_empty` | fixtures/reject/src/lib.rs:989:9 | | 1 | unsupported call to `_` | `std::ops::RangeTo::contains` | fixtures/reject/src/lib.rs:984:11 | | 1 | unsupported call to `_` | `std::ptr::addr_eq` | fixtures/reject/src/lib.rs:1712:14 | | 1 | unsupported call to `_` | `std::ptr::const_ptr::len` | fixtures/reject/src/lib.rs:1717:58 | -| 1 | unsupported call to `_` | `std::ptr::const_ptr::offset` | fixtures/reject/src/lib.rs:1713:73 | -| 1 | unsupported call to `_` | `std::ptr::copy_nonoverlapping` | fixtures/reject/src/lib.rs:1711:3 | -| 1 | unsupported call to `_` | `std::ptr::copy` | fixtures/reject/src/lib.rs:1710:3 | -| 1 | unsupported call to `_` | `std::ptr::mut_ptr::add` | fixtures/reject/src/lib.rs:1713:56 | | 1 | unsupported call to `_` | `std::std::Layout::new` | fixtures/reject/src/lib.rs:565:15 | | 1 | unsupported call to `_` | `std::std::alloc` | fixtures/reject/src/lib.rs:567:11 | | 1 | unsupported call to `_` | `std::std::dealloc` | fixtures/reject/src/lib.rs:570:3 | @@ -442,7 +464,7 @@ The type or module each refused type or path belongs to. ## Spans -
a raw pointer to `_` in `_`, which the crate may put another object in place of (49) +
a raw pointer to `_` in `_`, which the crate may put another object in place of (50) - fixtures/reject/src/lib.rs:608:10: a raw pointer to `(u32, u32)` in `Span2`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:669:2: a raw pointer to `(u32, u32)` in `Window`, which the crate may put another object in place of @@ -489,14 +511,43 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:1613:51: a raw pointer to `ByKeySet` in `(u16, ByKeySet)`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:1621:55: a raw pointer to `ByKeyReplace` in `(u16, ByKeyReplace)`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:1628:52: a raw pointer to `ByKeyTake` in `(u16, ByKeyTake)`, which the crate may put another object in place of -- fixtures/reject/src/lib.rs:2247:2: a raw pointer to `(u8, u8)` in `([Cloned; 2], (u8, u8))`, which the crate may put another object in place of -- fixtures/reject/src/lib.rs:2263:2: a raw pointer to `(u8, u8)` in `(Dropped, (u8, u8))`, which the crate may put another object in place of -- fixtures/reject/src/lib.rs:2281:13: a raw pointer to `(u8, u8)` in `([Greatest; 2], (u8, u8))`, which the crate may put another object in place of -- fixtures/reject/src/lib.rs:2312:2: a raw pointer to `(u8, u8)` in `([Touched; 2], (u8, u8))`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2192:10: a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2349:2: a raw pointer to `(u8, u8)` in `([Cloned; 2], (u8, u8))`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2365:2: a raw pointer to `(u8, u8)` in `(Dropped, (u8, u8))`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2383:13: a raw pointer to `(u8, u8)` in `([Greatest; 2], (u8, u8))`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2414:2: a raw pointer to `(u8, u8)` in `([Touched; 2], (u8, u8))`, which the crate may put another object in place of
-
unsupported call to `_` (25) +
a raw pointer to `_` in a variable assigned again while it may point there (23) + +- fixtures/reject/src/lib.rs:616:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:624:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:633:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:641:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:650:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:658:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:696:15: a raw pointer to `std::cell::UnsafeCell` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:709:15: a raw pointer to `std::cell::UnsafeCell` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1001:26: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1014:22: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1024:12: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1033:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1043:17: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1048:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1669:10: a raw pointer to `Target` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1682:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1697:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1733:26: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:1734:10: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2190:10: a raw pointer to `[u32; 3]` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2282:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2290:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2298:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there + +
+ +
unsupported call to `_` (21) - fixtures/reject/src/lib.rs:160:8: unsupported call to `std::ops::Range::::contains::` - fixtures/reject/src/lib.rs:301:10: unsupported call to `::hash::` @@ -516,40 +567,9 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:989:9: unsupported call to `std::ops::Range::<&mut u8>::is_empty` - fixtures/reject/src/lib.rs:1277:4: unsupported call to `core::num::::is_ascii_octdigit` - fixtures/reject/src/lib.rs:1636:13: unsupported call to `core::str::::as_bytes_mut` -- fixtures/reject/src/lib.rs:1710:3: unsupported call to `std::ptr::copy::<[u8; 4]>` -- fixtures/reject/src/lib.rs:1711:3: unsupported call to `std::ptr::copy_nonoverlapping::<[u8; 4]>` - fixtures/reject/src/lib.rs:1712:14: unsupported call to `std::ptr::addr_eq::<[u8; 4], [u8; 4]>` -- fixtures/reject/src/lib.rs:1713:56: unsupported call to `std::ptr::mut_ptr::::add` -- fixtures/reject/src/lib.rs:1713:73: unsupported call to `std::ptr::const_ptr::::offset` - fixtures/reject/src/lib.rs:1717:58: unsupported call to `std::ptr::const_ptr::::len` -- fixtures/reject/src/lib.rs:2174:2: unsupported call to `std::mem::size_of_val::` - -
- -
a raw pointer to `_` in a variable assigned again while it may point there (22) - -- fixtures/reject/src/lib.rs:616:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:624:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:633:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:641:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:650:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:658:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:696:15: a raw pointer to `std::cell::UnsafeCell` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:709:15: a raw pointer to `std::cell::UnsafeCell` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1001:26: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1014:22: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1024:12: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1033:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1043:17: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1048:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1669:10: a raw pointer to `Target` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1682:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1697:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1733:26: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:1734:10: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:2180:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:2188:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:2196:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2276:2: unsupported call to `std::mem::size_of_val::`
@@ -627,7 +647,7 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:83:2: an assignment through a reference to `std::sync::MutexGuard<'_, u8>` - fixtures/reject/src/lib.rs:107:2: an assignment through a reference to `std::pin::Pin>>` - fixtures/reject/src/lib.rs:214:2: an assignment through a reference to `std::pin::Pin<&mut Noisy>` -- fixtures/reject/src/lib.rs:2211:2: an assignment through a reference to `std::sync::LazyLock` +- fixtures/reject/src/lib.rs:2313:2: an assignment through a reference to `std::sync::LazyLock`
@@ -635,7 +655,15 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:21:4: `slice::fill` of `Reused`, whose `clone_from` is its own, which is not supported yet - fixtures/reject/src/lib.rs:286:4: `slice::clone_from_slice` of `Reused`, whose `clone_from` is its own, which is not supported yet -- fixtures/reject/src/lib.rs:2169:4: `Clone::clone_from` of `std::vec::Vec`, whose `clone_from` is its own, which is not supported yet +- fixtures/reject/src/lib.rs:2271:4: `Clone::clone_from` of `std::vec::Vec`, whose `clone_from` is its own, which is not supported yet + +
+ +
a copy between pointers to `_`, which JS keeps as an object that knows no array it is in (3) + +- fixtures/reject/src/lib.rs:1710:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:1711:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:2162:3: a copy between pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in
@@ -649,9 +677,17 @@ The type or module each refused type or path belongs to.
a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there (3) -- fixtures/reject/src/lib.rs:2164:2: a slice or a cell of a mutable reference to `Reused` that names no place, which a whole write would replace only there -- fixtures/reject/src/lib.rs:2206:3: a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which a whole write would replace only there -- fixtures/reject/src/lib.rs:2217:2: a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which a whole write would replace only there +- fixtures/reject/src/lib.rs:2266:2: a slice or a cell of a mutable reference to `Reused` that names no place, which a whole write would replace only there +- fixtures/reject/src/lib.rs:2308:3: a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which a whole write would replace only there +- fixtures/reject/src/lib.rs:2319:2: a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which a whole write would replace only there + +
+ +
an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in (3) + +- fixtures/reject/src/lib.rs:1713:56: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:1713:73: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:2161:13: an offset of a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in
@@ -699,10 +735,24 @@ The type or module each refused type or path belongs to.
+
`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 (2) + +- fixtures/reject/src/lib.rs:2225:5: `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/lib.rs:2226: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 + +
+ +
`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 (2) + +- fixtures/reject/src/lib.rs:2227: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/lib.rs:2228: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 + +
+
a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another (2) - fixtures/reject/src/lib.rs:555:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another -- fixtures/reject/src/lib.rs:1713:17: a cast from a pointer to `[u8; 4]` to one to `u8`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2177:13: a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another
@@ -713,6 +763,20 @@ The type or module each refused type or path belongs to.
+
a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust (2) + +- fixtures/reject/src/lib.rs:2250:18: a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust +- fixtures/reject/src/lib.rs:2253:21: a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust + +
+ +
a raw or mutable slice of a byte in no array of bytes (2) + +- fixtures/reject/src/lib.rs:2236:12: a raw or mutable slice of a byte in no array of bytes +- fixtures/reject/src/lib.rs:2237:19: a raw or mutable slice of a byte in no array of bytes + +
+
a raw pointer to a place of `()`, which has no address of its own (2) - fixtures/reject/src/lib.rs:728:15: a raw pointer to a place of `()`, which has no address of its own @@ -851,6 +915,30 @@ The type or module each refused type or path belongs to.
+
`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 (1) + +- fixtures/reject/src/lib.rs:2230: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 + +
+ +
`offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays (1) + +- fixtures/reject/src/lib.rs:2229:20: `offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays + +
+ +
`offset_from` of pointers to `()`, which has no bytes (1) + +- fixtures/reject/src/lib.rs:2172:13: `offset_from` of pointers to `()`, which has no bytes + +
+ +
`offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2165:36: `offset_from` of pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+
a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet (1) - fixtures/reject/src/lib.rs:209:40: a `collect` in place of `std::iter::Map, {closure@fixtures/reject/src/lib.rs:208:32: 208:40}>, std::ops::Range>, {closure@fixtures/reject/src/lib.rs:209:24: 209:32}>`, a zip std reads by index, holding values that drop, which is not supported yet @@ -869,12 +957,6 @@ The type or module each refused type or path belongs to.
-
a cast from `_` to `_`, which reads the bytes of one type as another (1) - -- fixtures/reject/src/lib.rs:1713:33: a cast from `*const [u8; 4]` to `*const u8`, which reads the bytes of one type as another - -
-
a coercion of `_` to `_`, a pointer to a slice (1) - fixtures/reject/src/lib.rs:1714:28: a coercion of `*const [u8; 4]` to `*const [u8]`, a pointer to a slice @@ -893,6 +975,12 @@ The type or module each refused type or path belongs to.
+
a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust (1) + +- fixtures/reject/src/lib.rs:2248:3: a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust + +
+
a dangling pointer to `_`, a value of no bytes made of more than structs and tuples (1) - fixtures/reject/src/lib.rs:737:11: a dangling pointer to `Only`, a value of no bytes made of more than structs and tuples @@ -923,6 +1011,24 @@ The type or module each refused type or path belongs to.
+
a pointer into `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2159:12: a pointer into `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+ +
a pointer into an array of `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2164:30: a pointer into an array of `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+ +
a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust (1) + +- fixtures/reject/src/lib.rs:2252:27: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust + +
+
a pointer's address, which JS has no counterpart of (1) - fixtures/reject/src/lib.rs:560:17: a pointer's address, which JS has no counterpart of @@ -953,9 +1059,27 @@ The type or module each refused type or path belongs to.
+
a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2163:11: a slice from a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+ +
a slice of `()`, which has no bytes, from a pointer (1) + +- fixtures/reject/src/lib.rs:2172:39: a slice of `()`, which has no bytes, from a pointer + +
+
a slice of one byte's place, which a byte slice can view only in an array of bytes (1) -- fixtures/reject/src/lib.rs:2158:2: a slice of one byte's place, which a byte slice can view only in an array of bytes +- fixtures/reject/src/lib.rs:2260:2: a slice of one byte's place, which a byte slice can view only in an array of bytes + +
+ +
a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust (1) + +- fixtures/reject/src/lib.rs:2251:6: a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust
From 19af10332b8d580b8e6a92bf2066d309aa20822e Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 07:57:40 -0800 Subject: [PATCH 23/34] fix(ts): normalize pointers through mutable parts, keep null and dangling in empty raw slices Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-unsafe.md | 3 +++ ts/fixtures/lang/src/elements.rs | 20 ++++++++++++++++++-- ts/runtime/rt.ts | 32 ++++++++++++++++++++++++-------- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/quest/m3/ts-unsafe.md b/quest/m3/ts-unsafe.md index ee40e2b2c..9f9163ea5 100644 --- a/quest/m3/ts-unsafe.md +++ b/quest/m3/ts-unsafe.md @@ -48,6 +48,9 @@ none of its own `unsafe`. `set_len` is refused, and a write past the length fails loudly at run time, since the JS array would grow by it. The spare slots need to live outside the array's length until `set_len` takes them. +- `ptr::slice_from_raw_parts` past what the array holds, which Rust allows until the + slice is read: the runtime makes the slice at once, so it fails there loudly. Deferring + the check needs a raw slice pointer kept as its pointer and length. - `UnsafeCell::raw_get`, `from_mut`, and `replace`, refused with their span: none is in moq's census at the pin. - A slice's pointer cast to an array's of its length, `*(s as *const [u8] as *const diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 24b897802..d2a4378ed 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -255,8 +255,9 @@ pub fn inline(data: &[u8], out: &mut Vec) { /// Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as /// the array of one Rust treats it as, and into a mutable part of an array, measured from /// the array, the pointer an empty slice keeps, within a shared part, which JS copies, -/// memory nothing wrote copied, null and dangling pointers moved by address and cast from -/// pointers to arrays, and the crate's functions named as std's. +/// a `&mut` into that part, memory nothing wrote copied, null and dangling pointers moved by +/// address, kept by empty raw slices, and cast from pointers to arrays, and the crate's +/// functions named as std's. pub fn edges(data: &[u8], out: &mut Vec) { for &n in data { let bytes = [n, 1, 2, 3]; @@ -295,6 +296,10 @@ pub fn edges(data: &[u8], out: &mut Vec) { unsafe { out.push(at.offset_from(base) as u8); *at += 5; + // A `&mut` into the part, measured from the array and moved out of the part. + let got: *mut u32 = part.get_mut(0).unwrap(); + out.push(got.offset_from(base) as u8); + out.push(*got.sub(1) as u8); let empty = slice::from_raw_parts(at.add(1), 0); out.push(empty.as_ptr().offset_from(base) as u8); let none = slice::from_raw_parts(bytes.as_ptr().add(2), 0); @@ -352,6 +357,17 @@ pub fn edges(data: &[u8], out: &mut Vec) { .wrapping_offset(isize::from(n % 3) - 1) .is_null() as u8, ); + // Empty raw slices from null and dangling pointers, which keep them. + out.push((ptr::slice_from_raw_parts(ptr::null::(), 0) as *const u32).is_null() as u8); + out.push((ptr::slice_from_raw_parts(ptr::null::(), 0) as *const u8).is_null() as u8); + let dangling = NonNull::::dangling().as_ptr(); + out.push( + (ptr::slice_from_raw_parts(dangling, 0) as *const u16) + .wrapping_sub(1) + .is_null() as u8, + ); + let none = unsafe { slice::from_raw_parts(NonNull::::dangling().as_ptr(), 0) }; + out.push(none.as_ptr().wrapping_sub(1).is_null() as u8); // Null and dangling pointers to arrays, cast to ones to their first elements. out.push((ptr::null::<[u32; 2]>() as *const u32).is_null() as u8); out.push( diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index acfd8ab41..f049d9c20 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -161,16 +161,16 @@ export type CowOf = Cow | ByteCow; * or dangling pointer to an array is one to its first element, at the same address. */ export function sliceAsPtr(s: { [i: number]: T }, index = 0): Place { - if (s === NULL) return NULL; - if (s === DANGLING) return DANGLING; + if (s === NULL || s === NULL_SLICE) return NULL; + if (s === DANGLING || s === DANGLING_SLICE) return DANGLING; const view = (s as { [VIEW]?: View })[VIEW]; return view ? new AtIndex(view.s, view.start + index) : new AtIndex(s, index); } /** The pointer to byte `index` of `s`, or `s` if it is null or dangling, as `sliceAsPtr`'s. */ export function byteSliceAsPtr(s: ByteSlice, index = 0): Place { - if (s === NULL) return NULL; - if (s === DANGLING) return DANGLING; + if (s === NULL || s === NULL_BYTES) return NULL; + if (s === DANGLING || s === DANGLING_BYTES) return DANGLING; return new AtIndex(s.data, s.start + index); } @@ -237,7 +237,11 @@ class Lone implements ProxyHandler { * and dangling pointers point into an array of none, so any access through them fails. */ function elemOf(p: Place): AtIndex { - if (p instanceof AtIndex) return p; + if (p instanceof AtIndex) { + // A pointer into a mutable part of an array points into the array. + const view = (p.array as { [VIEW]?: View })[VIEW]; + return view ? new AtIndex(view.s, view.start + p.index) : p; + } if (p === NULL || p === DANGLING) return new AtIndex([] as T[], 0); let lone = LONE.get(p) as T[] | undefined; if (!lone) { @@ -310,10 +314,17 @@ export function ptrCopy(src: Place, dst: Place, n: number, copy?: (valu for (let i = 0; i < n; i++) to.array[to.index + i] = copy && values[i] !== undefined ? copy(values[i]) : values[i]; } +/** + * The empty slices from a null and a dangling pointer, which keep the pointer they were + * made from, as `sliceAsPtr` gives back; `NULL_BYTES` and `DANGLING_BYTES` are their bytes'. + */ +const NULL_SLICE: never[] = /* @__PURE__ */ Object.freeze([]) as never[]; +const DANGLING_SLICE: never[] = /* @__PURE__ */ Object.freeze([]) as never[]; + /** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` elements from `p`. */ export function ptrSlice(p: Place, len: number): T[] { - // One of none from a dangling pointer points nowhere. - if (len === 0 && (p === NULL || p === DANGLING)) return []; + if (len === 0 && p === NULL) return NULL_SLICE; + if (len === 0 && p === DANGLING) return DANGLING_SLICE; const at = elemOf(p); at.reach(len); const array = at.array as T[]; @@ -326,7 +337,8 @@ export function ptrSlice(p: Place, len: number): T[] { * writes them while it lives. */ export function ptrByteSlice(p: Place, len: number, shared: boolean): ByteSlice { - if (len === 0 && (p === NULL || p === DANGLING)) return new ByteSlice(new Uint8Array(0), 0, 0); + if (len === 0 && p === NULL) return NULL_BYTES; + if (len === 0 && p === DANGLING) return DANGLING_BYTES; const at = elemOf(p); at.reach(len); if (at.array instanceof Uint8Array) return new ByteSlice(at.array, at.index, len); @@ -1074,6 +1086,10 @@ export class ByteSlice { } } +/** The empty byte slices from a null and a dangling pointer, as `NULL_SLICE` and `DANGLING_SLICE`. */ +const NULL_BYTES = /* @__PURE__ */ Object.freeze(new ByteSlice(new Uint8Array(0), 0, 0)); +const DANGLING_BYTES = /* @__PURE__ */ Object.freeze(new ByteSlice(new Uint8Array(0), 0, 0)); + /** All of `bytes` as a slice, as `&[u8; N]` coerces to `&[u8]`. */ export function byteSliceFrom(bytes: Uint8Array): ByteSlice { return new ByteSlice(bytes, 0, bytes.length); From c209b235cb2d157d990bca5f19310b932b0cdb2e Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 08:11:30 -0800 Subject: [PATCH 24/34] fix(ts): keep the empty byte slices' sentinels tree-shakeable, regenerate elements Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.ts | 46 ++++++++++++++++----------- ts/runtime/rt.ts | 4 +-- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index bca67c3e2..bad197217 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -204,12 +204,13 @@ export function inline(data: rt.ByteSlice, out: rt.ByteVec): void { * Pointers wrapped around wasm32's 32-bit addresses and back, to a value in no array, as * the array of one Rust treats it as, and into a mutable part of an array, measured from * the array, the pointer an empty slice keeps, within a shared part, which JS copies, - * memory nothing wrote copied, null and dangling pointers moved by address and cast from - * pointers to arrays, and the crate's functions named as std's. + * a `&mut` into that part, memory nothing wrote copied, null and dangling pointers moved by + * address, kept by empty raw slices, and cast from pointers to arrays, and the crate's + * functions named as std's. */ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { - let _189!: rt.Result<[rt.u32, rt.u32], rt.u8>; - let _195!: rt.u8; + let _200!: rt.Result<[rt.u32, rt.u32], rt.u8>; + let _206!: rt.u8; for (const n of rt.byteSliceView(data)) { const bytes: Uint8Array = Uint8Array.of(n, 1, 2, 3); const p: rt.Place = rt.byteSliceAsPtr(rt.byteSliceFrom(bytes)); @@ -242,6 +243,9 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { const at2: rt.Place = rt.sliceAsPtr(part, 1); rt.byteVecPush(out, rt.ptrOffsetFrom(at2, base) & 0xff); at2.v = rt.u32Add(at2.v, 5); + const got: rt.Place = rt.optionUnwrap(rt.sliceGetMut(part, 0)); + rt.byteVecPush(out, rt.ptrOffsetFrom(got, base) & 0xff); + rt.byteVecPush(out, rt.ptrAdd(got, -1).v & 0xff); rt.byteVecPush(out, rt.ptrOffsetFrom(rt.sliceAsPtr(rt.ptrSlice(rt.ptrAdd(at2, 1), 0)), base) & 0xff); rt.byteVecPush(out, rt.ptrOffsetFrom(rt.byteSliceAsPtr(rt.ptrByteSlice(rt.ptrAdd(rt.byteSliceAsPtr(rt.byteSliceFrom(bytes)), 2), 0, true)), p) & 0xff); rt.iterExtendBytes(out, rt.iterMap(rt.iterSlice(a), (_2: rt.u32): rt.u8 => { @@ -251,11 +255,11 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { const first: rt.Place = rt.sliceAsPtr(rt.sliceRange(lanes, 1, 3)); rt.byteVecPush(out, rt.ptrAdd(first, 1).v & 0xff); rt.byteVecPush(out, rt.ptrOffsetFrom(rt.ptrAdd(first, 2), first) & 0xff); - const _159: rt.u32[] = rt.ptrSlice(rt.ptrAdd(first, 1), 1); - rt.idx(_159, 0); - rt.byteVecPush(out, _159[0] & 0xff); - const _166: [rt.u32, rt.u8] = [n as rt.u32, n]; - let x: rt.u32 = _166[0]; + const _170: rt.u32[] = rt.ptrSlice(rt.ptrAdd(first, 1), 1); + rt.idx(_170, 0); + rt.byteVecPush(out, _170[0] & 0xff); + const _177: [rt.u32, rt.u8] = [n as rt.u32, n]; + let x: rt.u32 = _177[0]; let y: rt.u8 = n; rt.byteVecPush(out, offsetFrom(rt.place(() => x, (value3) => x = value3), rt.place(() => x, (value4) => x = value4)) & 0xff); rt.byteVecPush(out, fromRawPartsMut(rt.place(() => y, (value5) => y = value5), 2)); @@ -268,31 +272,35 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { return copy$mem_MaybeUninit_Result_tuple2_u32_u32_u8(item); })), rt.sliceAsPtr(dst), 2, copy$mem_MaybeUninit_Result_tuple2_u32_u32_u8); if (!((n & 1) === 0)) { - _189 = rt.Err(n); + _200 = rt.Err(n); } else { - _189 = rt.Ok([x, 1] as [rt.u32, rt.u32]); + _200 = rt.Ok([x, 1] as [rt.u32, rt.u32]); } const uninit = rt.place(() => dst[1], (value6) => dst[1] = value6); - uninit.v = _189; + uninit.v = _200; uninit; - const _196: rt.Result<[rt.u32, rt.u32], rt.u8> = dst[1]; - if (_196.ok) { - _195 = rt.u32Add(_196.value[0], _196.value[1]) & 0xff; + const _207: rt.Result<[rt.u32, rt.u32], rt.u8> = dst[1]; + if (_207.ok) { + _206 = rt.u32Add(_207.value[0], _207.value[1]) & 0xff; } else { - _195 = _196.error; + _206 = _207.error; } - rt.byteVecPush(out, _195); + rt.byteVecPush(out, _206); const null_: rt.Place = rt.NULL; rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(null_, 1, 4, 4), -1, 4, 4) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(null_, n as rt.usize, 4, 4) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(null_, 1 << 30, 4, 4) === rt.NULL ? 1 : 0); - const _226: [rt.Place, rt.Place] = [rt.ptrWrappingAdd(null_, rt.usizeAdd(n as rt.usize, 1), 4, 4), rt.ptrWrappingAdd(null_, rt.usizeAdd(n as rt.usize, 1), 4, 4)]; - rt.byteVecPush(out, rt.ptrOffsetFrom(_226[0], _226[1]) & 0xff); + const _237: [rt.Place, rt.Place] = [rt.ptrWrappingAdd(null_, rt.usizeAdd(n as rt.usize, 1), 4, 4), rt.ptrWrappingAdd(null_, rt.usizeAdd(n as rt.usize, 1), 4, 4)]; + rt.byteVecPush(out, rt.ptrOffsetFrom(_237[0], _237[1]) & 0xff); const dangling: rt.Place = rt.DANGLING; rt.byteVecPush(out, rt.ptrWrappingAdd(dangling, -1, 2, 2) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(dangling, 4294967295, 2, 2) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(dangling, -((n as rt.usize)), 2, 2), n as rt.usize, 2, 2) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.DANGLING, rt.isizeSub(rt.u8Rem(n, 3) as rt.isize, 1), 8, 8) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.sliceAsPtr({ v: rt.ptrSlice(rt.NULL, 0) }.v) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.byteSliceAsPtr({ v: rt.ptrByteSlice(rt.NULL, 0, false) }.v) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr({ v: rt.ptrSlice(rt.DANGLING, 0) }.v), -1, 2, 2) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.byteSliceAsPtr(rt.ptrByteSlice(rt.DANGLING, 0, true)), -1, 1, 1) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.sliceAsPtr(rt.NULL) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.byteSliceAsPtr(rt.NULL), n as rt.usize, 1, 1) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr(rt.DANGLING), -1, 2, 2) === rt.NULL ? 1 : 0); diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index f049d9c20..142d1e219 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -1087,8 +1087,8 @@ export class ByteSlice { } /** The empty byte slices from a null and a dangling pointer, as `NULL_SLICE` and `DANGLING_SLICE`. */ -const NULL_BYTES = /* @__PURE__ */ Object.freeze(new ByteSlice(new Uint8Array(0), 0, 0)); -const DANGLING_BYTES = /* @__PURE__ */ Object.freeze(new ByteSlice(new Uint8Array(0), 0, 0)); +const NULL_BYTES = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ new ByteSlice(/* @__PURE__ */ new Uint8Array(0), 0, 0)); +const DANGLING_BYTES = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ new ByteSlice(/* @__PURE__ */ new Uint8Array(0), 0, 0)); /** All of `bytes` as a slice, as `&[u8; N]` coerces to `&[u8]`. */ export function byteSliceFrom(bytes: Uint8Array): ByteSlice { From b091e2eb25de6a597b690201724bb5fe06e8bb1c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 08:16:15 -0800 Subject: [PATCH 25/34] fix(ts): keep the pointer an empty slice from no array was made from Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/src/elements.rs | 9 ++++++++ ts/runtime/rt.ts | 39 +++++++++++++++++--------------- ts/src/lower/pointer.rs | 8 +++++++ 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index d2a4378ed..c02a60934 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -366,8 +366,17 @@ pub fn edges(data: &[u8], out: &mut Vec) { .wrapping_sub(1) .is_null() as u8, ); + out.push(ptr::slice_from_raw_parts(ptr::null::(), 0).is_null() as u8); + out.push(ptr::slice_from_raw_parts_mut(ptr::null_mut::(), 0).is_null() as u8); let none = unsafe { slice::from_raw_parts(NonNull::::dangling().as_ptr(), 0) }; out.push(none.as_ptr().wrapping_sub(1).is_null() as u8); + out.push(none[0..0].as_ptr().wrapping_sub(1).is_null() as u8); + let off = ptr::null::().wrapping_add(usize::from(n % 3) + 1); + out.push( + (ptr::slice_from_raw_parts(off, 0) as *const u32) + .wrapping_sub(usize::from(n % 3) + 1) + .is_null() as u8, + ); // Null and dangling pointers to arrays, cast to ones to their first elements. out.push((ptr::null::<[u32; 2]>() as *const u32).is_null() as u8); out.push( diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 142d1e219..fdbabf2b9 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -161,17 +161,18 @@ export type CowOf = Cow | ByteCow; * or dangling pointer to an array is one to its first element, at the same address. */ export function sliceAsPtr(s: { [i: number]: T }, index = 0): Place { - if (s === NULL || s === NULL_SLICE) return NULL; - if (s === DANGLING || s === DANGLING_SLICE) return DANGLING; + if (s === NULL) return NULL; + if (s === DANGLING) return DANGLING; const view = (s as { [VIEW]?: View })[VIEW]; - return view ? new AtIndex(view.s, view.start + index) : new AtIndex(s, index); + const [array, at] = view ? [view.s, view.start + index] : [s, index]; + return (EMPTY.get(array) as Place | undefined) ?? new AtIndex(array, at); } /** The pointer to byte `index` of `s`, or `s` if it is null or dangling, as `sliceAsPtr`'s. */ export function byteSliceAsPtr(s: ByteSlice, index = 0): Place { - if (s === NULL || s === NULL_BYTES) return NULL; - if (s === DANGLING || s === DANGLING_BYTES) return DANGLING; - return new AtIndex(s.data, s.start + index); + if (s === NULL) return NULL; + if (s === DANGLING) return DANGLING; + return (EMPTY.get(s.data) as Place | undefined) ?? new AtIndex(s.data, s.start + index); } /** The pointer to the first byte of `v`'s storage. */ @@ -315,16 +316,19 @@ export function ptrCopy(src: Place, dst: Place, n: number, copy?: (valu } /** - * The empty slices from a null and a dangling pointer, which keep the pointer they were - * made from, as `sliceAsPtr` gives back; `NULL_BYTES` and `DANGLING_BYTES` are their bytes'. + * The pointer each empty slice made from a pointer into no array was made from, by the + * storage every part of it shares, which `sliceAsPtr` and `byteSliceAsPtr` give back. */ -const NULL_SLICE: never[] = /* @__PURE__ */ Object.freeze([]) as never[]; -const DANGLING_SLICE: never[] = /* @__PURE__ */ Object.freeze([]) as never[]; +const EMPTY = /* @__PURE__ */ new WeakMap>(); /** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` elements from `p`. */ export function ptrSlice(p: Place, len: number): T[] { - if (len === 0 && p === NULL) return NULL_SLICE; - if (len === 0 && p === DANGLING) return DANGLING_SLICE; + // None from a pointer into no array, such as null's or a dangling one's, keeps it. + if (len === 0 && !(p instanceof AtIndex)) { + const none = Object.freeze([]) as unknown as T[]; + EMPTY.set(none, p); + return none; + } const at = elemOf(p); at.reach(len); const array = at.array as T[]; @@ -337,8 +341,11 @@ export function ptrSlice(p: Place, len: number): T[] { * writes them while it lives. */ export function ptrByteSlice(p: Place, len: number, shared: boolean): ByteSlice { - if (len === 0 && p === NULL) return NULL_BYTES; - if (len === 0 && p === DANGLING) return DANGLING_BYTES; + if (len === 0 && !(p instanceof AtIndex)) { + const none = new Uint8Array(0); + EMPTY.set(none, p); + return new ByteSlice(none, 0, 0); + } const at = elemOf(p); at.reach(len); if (at.array instanceof Uint8Array) return new ByteSlice(at.array, at.index, len); @@ -1086,10 +1093,6 @@ export class ByteSlice { } } -/** The empty byte slices from a null and a dangling pointer, as `NULL_SLICE` and `DANGLING_SLICE`. */ -const NULL_BYTES = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ new ByteSlice(/* @__PURE__ */ new Uint8Array(0), 0, 0)); -const DANGLING_BYTES = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ new ByteSlice(/* @__PURE__ */ new Uint8Array(0), 0, 0)); - /** All of `bytes` as a slice, as `&[u8; N]` coerces to `&[u8]`. */ export function byteSliceFrom(bytes: Uint8Array): ByteSlice { return new ByteSlice(bytes, 0, bytes.length); diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 8e1748085..0a893fbc0 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -478,6 +478,14 @@ pub fn call<'tcx>( ("ptr" | "NonNull", "addr" | "expose_provenance" | "with_addr" | "map_addr" | "without_provenance") => { b.err(span, "a pointer's address, which JS has no counterpart of") } + // A raw pointer to a slice is an `rt.Place` of it, null where its data pointer is. + ("ptr", "is_null") if let ty::Slice(elem) = pointee().kind() => { + let f = match elem.kind() { + ty::Uint(ty::UintTy::U8) => "byteSliceAsPtr", + _ => "sliceAsPtr", + }; + Expr::rt(f, vec![arg().member("v")]).binary("===", Expr::Rt("NULL".into())) + } ("ptr", "is_null") => arg().binary("===", Expr::Rt("NULL".into())), ("NonNull", "as_mut") if bytes(b, pointee()) => to_reference(arg()), // The same pointer, as another type of the same pointee. From 33d0b07b6a92d1048699ea1e4e230fc994ec1107 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 08:25:48 -0800 Subject: [PATCH 26/34] fix(ts): find an empty slice's null pointer, which ?? passes over Co-Authored-By: Claude Opus 5.5 --- ts/runtime/rt.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index fdbabf2b9..1b28e5261 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -165,14 +165,18 @@ export function sliceAsPtr(s: { [i: number]: T }, index = 0): Place { if (s === DANGLING) return DANGLING; const view = (s as { [VIEW]?: View })[VIEW]; const [array, at] = view ? [view.s, view.start + index] : [s, index]; - return (EMPTY.get(array) as Place | undefined) ?? new AtIndex(array, at); + // Null's is `null`, which `??` would pass over. + const from = EMPTY.get(array) as Place | undefined; + return from !== undefined ? from : new AtIndex(array, at); } /** The pointer to byte `index` of `s`, or `s` if it is null or dangling, as `sliceAsPtr`'s. */ export function byteSliceAsPtr(s: ByteSlice, index = 0): Place { if (s === NULL) return NULL; if (s === DANGLING) return DANGLING; - return (EMPTY.get(s.data) as Place | undefined) ?? new AtIndex(s.data, s.start + index); + const from = EMPTY.get(s.data) as Place | undefined; + if (from !== undefined) return from; + return new AtIndex(s.data, s.start + index); } /** The pointer to the first byte of `v`'s storage. */ From 1c170e3233294046cee0b97dd2935f3bbb76c3ab Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 08:42:46 -0800 Subject: [PATCH 27/34] fix(ts): unsize a raw pointer to an array as the place of its slice, keep empty parts' pointers Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/src/elements.rs | 11 +++++++++++ ts/runtime/rt.ts | 5 +++-- ts/src/lower/mir/rvalue.rs | 7 +++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index c02a60934..3db920eba 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -371,6 +371,17 @@ pub fn edges(data: &[u8], out: &mut Vec) { let none = unsafe { slice::from_raw_parts(NonNull::::dangling().as_ptr(), 0) }; out.push(none.as_ptr().wrapping_sub(1).is_null() as u8); out.push(none[0..0].as_ptr().wrapping_sub(1).is_null() as u8); + let wide = unsafe { slice::from_raw_parts(NonNull::::dangling().as_ptr(), 0) }; + out.push(wide[0..0].as_ptr().wrapping_sub(1).is_null() as u8); + out.push(wide.get(0..0).unwrap().as_ptr().wrapping_sub(1).is_null() as u8); + // A raw pointer to an array, as one to a slice of it. + let unsized_null: *const [u32] = ptr::null::<[u32; 3]>(); + out.push(unsized_null.is_null() as u8); + let row = [u32::from(n), 9, 10]; + let unsized_lanes: *const [u32] = &raw const row; + out.push(unsized_lanes.is_null() as u8); + out.push(unsafe { (*unsized_lanes)[1] } as u8); + out.push(unsafe { (unsized_lanes as *const u32).add(2).read() } as u8); let off = ptr::null::().wrapping_add(usize::from(n % 3) + 1); out.push( (ptr::slice_from_raw_parts(off, 0) as *const u32) diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 1b28e5261..250b5ce86 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -514,7 +514,8 @@ export function rangeCheck(length: number, start: number, end: number): void { /** `&s[start..end]`, with `end` defaulting to the length. */ export function sliceRange(s: T[], start: number, end: number = s.length): T[] { rangeCheck(s.length, start, end); - return s.slice(start, end); + // An empty slice's part is it, which keeps the pointer it was made from. + return s.length === 0 ? s : s.slice(start, end); } /** @@ -603,7 +604,7 @@ export function sliceRangeMut(s: T[], start: number, end: number = s.length): /** `get` of `start..end`: the part, or `undefined` unless it is within the slice. */ export function sliceGetRange(s: T[], start: number, end: number = s.length): T[] | undefined { - return start <= end && end <= s.length ? s.slice(start, end) : undefined; + return start <= end && end <= s.length ? (s.length === 0 ? s : s.slice(start, end)) : undefined; } /** `get_mut` of `start..end`: the runtime's view of it, or `undefined` as `sliceGetRange`. */ diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 3bb0d8de5..2d2904954 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -342,6 +342,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { _ => self.err(span, format!("a coercion to `{to}`")), } } + // A raw pointer to a slice is an `rt.Place` of it; one to an array is the array. + PointerCoercion::Unsize + if from.is_raw_ptr() && to.builtin_deref(true).is_some_and(|t| t.is_slice()) => + { + let value = self.operand(op, span, out); + Expr::Object(vec![(Key::Name("v".into()), value)]) + } PointerCoercion::Unsize | PointerCoercion::ReifyFnPointer(_) | PointerCoercion::ClosureFnPointer(_) From a15976ebb8d6b560b5687073c1c6a735a88f95df Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 08:49:24 -0800 Subject: [PATCH 28/34] test(ts): read an unsized raw slice pointer through a cast Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-unsafe.md | 2 ++ ts/fixtures/lang/src/elements.rs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/quest/m3/ts-unsafe.md b/quest/m3/ts-unsafe.md index 9f9163ea5..00a032d29 100644 --- a/quest/m3/ts-unsafe.md +++ b/quest/m3/ts-unsafe.md @@ -51,6 +51,8 @@ none of its own `unsafe`. - `ptr::slice_from_raw_parts` past what the array holds, which Rust allows until the slice is read: the runtime makes the slice at once, so it fails there loudly. Deferring the check needs a raw slice pointer kept as its pointer and length. +- An element read through a dereferenced raw slice pointer, `(*p)[i]` for a `*const [T]`: + the place's `v` is skipped, so the output fails `tsc` at translation. - `UnsafeCell::raw_get`, `from_mut`, and `replace`, refused with their span: none is in moq's census at the pin. - A slice's pointer cast to an array's of its length, `*(s as *const [u8] as *const diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 3db920eba..8608f3961 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -380,7 +380,6 @@ pub fn edges(data: &[u8], out: &mut Vec) { let row = [u32::from(n), 9, 10]; let unsized_lanes: *const [u32] = &raw const row; out.push(unsized_lanes.is_null() as u8); - out.push(unsafe { (*unsized_lanes)[1] } as u8); out.push(unsafe { (unsized_lanes as *const u32).add(2).read() } as u8); let off = ptr::null::().wrapping_add(usize::from(n % 3) + 1); out.push( From b47bb4ce5c5e7daf117b7a65e43748dad2eacad6 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 08:58:39 -0800 Subject: [PATCH 29/34] chore(ts): regenerate the elements snapshot Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index 0bfe3c68e..b6d4b8a69 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -305,6 +305,14 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { const none2: rt.ByteSlice = rt.ptrByteSlice(rt.DANGLING, 0, true); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.byteSliceAsPtr(none2), -1, 1, 1) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.byteSliceAsPtr(rt.byteSliceRange(none2, 0, 0)), -1, 1, 1) === rt.NULL ? 1 : 0); + const wide2: rt.u16[] = rt.ptrSlice(rt.DANGLING, 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr(rt.sliceRange(wide2, 0, 0)), -1, 2, 2) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr(rt.optionUnwrap(rt.sliceGetRange(wide2, 0, 0))), -1, 2, 2) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.sliceAsPtr({ v: rt.NULL }.v) === rt.NULL ? 1 : 0); + const row: rt.u32[] = [n as rt.u32, 9, 10]; + const unsizedLanes: rt.Place = { v: row }; + rt.byteVecPush(out, rt.sliceAsPtr(unsizedLanes.v) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrAdd(rt.sliceAsPtr(unsizedLanes.v), 2).v & 0xff); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr({ v: rt.ptrSlice(rt.ptrWrappingAdd(rt.NULL, rt.usizeAdd(rt.u8Rem(n, 3) as rt.usize, 1), 4, 4), 0) }.v), -rt.usizeAdd(rt.u8Rem(n, 3) as rt.usize, 1), 4, 4) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.sliceAsPtr(rt.NULL) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.byteSliceAsPtr(rt.NULL), n as rt.usize, 1, 1) === rt.NULL ? 1 : 0); From f270a3561ea4ea481527103aee507c8365e5b266 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:03:30 -0800 Subject: [PATCH 30/34] feat(ts): keep a raw slice pointer as its start and length Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-unsafe.md | 8 +- ts/fixtures/lang/expected/core/option.ts | 7 + ts/fixtures/lang/expected/elements.Inline.ts | 4 +- ts/fixtures/lang/expected/elements.Storage.ts | 4 +- ts/fixtures/lang/expected/elements.ts | 186 ++++++++++++++++-- ts/fixtures/lang/expected/harness.ts | 23 ++- ts/fixtures/lang/src/elements.rs | 96 +++++++++ ts/fixtures/lang/src/harness.rs | 2 + ts/runtime/rt.ts | 58 +++++- ts/runtime/rt/iter.ts | 4 +- ts/src/lower/mir/origin.rs | 4 + ts/src/lower/mir/place.rs | 13 +- ts/src/lower/mir/rvalue.rs | 44 ++++- ts/src/lower/mod.rs | 2 +- ts/src/lower/pointer.rs | 95 ++++++--- 15 files changed, 478 insertions(+), 72 deletions(-) diff --git a/quest/m3/ts-unsafe.md b/quest/m3/ts-unsafe.md index 00a032d29..63185fe50 100644 --- a/quest/m3/ts-unsafe.md +++ b/quest/m3/ts-unsafe.md @@ -37,6 +37,9 @@ none of its own `unsafe`. translates, but it spills to memory from `alloc::alloc`, which this Plan refuses, so it translates whole only once the maintainer decides how a typed allocation (`Layout::array::`, then a cast to `*mut T`) is modeled or that smallvec is mapped. +- Decided with the maintainer on 2026-11-02: a raw slice pointer is `rt.RawSlice`, its + first element's pointer and its length, read through only where Rust reads, and an empty + slice from a pointer into no array keeps that pointer through every part of it. - `ManuallyDrop` as `T` with its drop skipped, as `MaybeUninit` is: crossbeam-epoch's `Local` reaches it, though crossbeam's tagged pointers stay refused. - A union over a type parameter, whose layout has no fixed offsets until an instance @@ -48,11 +51,6 @@ none of its own `unsafe`. `set_len` is refused, and a write past the length fails loudly at run time, since the JS array would grow by it. The spare slots need to live outside the array's length until `set_len` takes them. -- `ptr::slice_from_raw_parts` past what the array holds, which Rust allows until the - slice is read: the runtime makes the slice at once, so it fails there loudly. Deferring - the check needs a raw slice pointer kept as its pointer and length. -- An element read through a dereferenced raw slice pointer, `(*p)[i]` for a `*const [T]`: - the place's `v` is skipped, so the output fails `tsc` at translation. - `UnsafeCell::raw_get`, `from_mut`, and `replace`, refused with their span: none is in moq's census at the pin. - A slice's pointer cast to an array's of its length, `*(s as *const [u8] as *const diff --git a/ts/fixtures/lang/expected/core/option.ts b/ts/fixtures/lang/expected/core/option.ts index fba0b75dd..cc5889673 100644 --- a/ts/fixtures/lang/expected/core/option.ts +++ b/ts/fixtures/lang/expected/core/option.ts @@ -1092,6 +1092,13 @@ export function mapOr$1w5qtj7(self: undefined | rt.None, default_: rt.u8, f: (a0 return f(self); } +export function mapOr$0grxkdu(self: rt.u32[] | undefined, default_: rt.u32, f: (a0: rt.u32[]) => rt.u32): rt.u32 { + if (self === undefined) { + return default_; + } + return f(self); +} + export function mapOr$1giiemz(self: rt.Place | undefined, default_: rt.u32, f: (a0: rt.Place) => rt.u32): rt.u32 { if (self === undefined) { return default_; diff --git a/ts/fixtures/lang/expected/elements.Inline.ts b/ts/fixtures/lang/expected/elements.Inline.ts index e47c3bd8b..269030506 100644 --- a/ts/fixtures/lang/expected/elements.Inline.ts +++ b/ts/fixtures/lang/expected/elements.Inline.ts @@ -97,12 +97,12 @@ export function remove$sync_Arc_u8(self: Inline>, at: rt.usize): r export function drop$ops_Drop_Option_tuple2_u8_u8(self: Inline<[rt.u8, rt.u8] | undefined>): void { const len: rt.usize = self.len; - { v: rt.ptrSlice(ptr$Option_tuple2_u8_u8(self), len) }; + new rt.RawSlice(ptr$Option_tuple2_u8_u8(self), len); } export function drop$ops_Drop_sync_Arc_u8(self: Inline>): void { const len: rt.usize = self.len; - rt.dropAll({ v: rt.ptrSlice(ptr$sync_Arc_u8(self), len) }.v); + rt.dropAll(rt.rawSliceDeref(new rt.RawSlice(ptr$sync_Arc_u8(self), len))); } //# sourceMappingURL=elements.Inline.ts.map diff --git a/ts/fixtures/lang/expected/elements.Storage.ts b/ts/fixtures/lang/expected/elements.Storage.ts index 3604fa536..195975e23 100644 --- a/ts/fixtures/lang/expected/elements.Storage.ts +++ b/ts/fixtures/lang/expected/elements.Storage.ts @@ -19,7 +19,7 @@ export class Storage { function new_(capacity: rt.usize): Storage { const boxed: rt.ByteSlice = rt.byteVecSlice(rt.byteVecOf(0, capacity)); const capacity2: rt.usize = boxed.length; - return new Storage(rt.byteSliceAsPtr({ v: boxed }.v), capacity2); + return new Storage(rt.rawByteSliceOf(boxed).ptr, capacity2); } export function write(self: Storage, offset: rt.usize, src: rt.ByteSlice): void { @@ -35,7 +35,7 @@ export function read(self: Storage, len: rt.usize): rt.ByteSlice { } export function drop$ops_Drop(self: Storage): void { - (({ v: rt.ptrByteSlice(self.data, self.capacity, false) }.v)); + rt.rawByteSliceDeref(new rt.RawSlice(self.data, self.capacity), false); } export { new_ as new }; diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index b6d4b8a69..e3c751cb8 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -3,7 +3,7 @@ import * as rt from "./rt.ts"; import { copy$Option_tuple2_u8_u8, copy$array_Option_tuple2_u8_u8, copy$mem_MaybeUninit_Result_tuple2_u32_u32_u8, drop$elements_Inline_Option_tuple2_u8_u8, drop$elements_Inline_sync_Arc_u8 } from "./lib.ts"; import { thenSome$03chm1q } from "./core/bool.ts"; -import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1fmzefe as option_map$1fmzefe, mapOr$1nlkf9b as option_mapOr$1nlkf9b } from "./core/option.ts"; +import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1fmzefe as option_map$1fmzefe, mapOr$0grxkdu as option_mapOr$0grxkdu, mapOr$1nlkf9b as option_mapOr$1nlkf9b } from "./core/option.ts"; import { offsetFrom } from "./elements/mine/ptr.ts"; import { fromRawPartsMut } from "./elements/mine/raw.ts"; import * as Inline from "./elements.Inline.ts"; @@ -75,9 +75,9 @@ export function arrays(data: rt.ByteSlice, out: rt.ByteVec): void { const _40: rt.Place = rt.ptrAdd(rt.ptrAdd(p, 3), -3); _40.v = rt.u16Add(_40.v, 2); rt.byteVecPush(out, rt.ptrOffsetFrom(rt.ptrAdd(p, 4), p) & 0xff); - const tail: rt.Place = { v: rt.ptrSlice(rt.ptrAdd(p, i), rt.usizeSub(5, i)) }; - rt.byteVecPush(out, tail.v.length & 0xff); - rt.sliceAsPtr(tail.v).v = 7; + const tail: rt.RawSlice = new rt.RawSlice(rt.ptrAdd(p, i), rt.usizeSub(5, i)); + rt.byteVecPush(out, rt.rawSliceDeref(tail).length & 0xff); + tail.ptr.v = 7; const unit: rt.Place = rt.DANGLING_UNIT; n as rt.usize; rt.byteVecPush(out, unit === unit ? 1 : 0); @@ -297,29 +297,187 @@ export function edges(data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecPush(out, rt.ptrWrappingAdd(dangling, 4294967295, 2, 2) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.ptrWrappingAdd(dangling, -((n as rt.usize)), 2, 2), n as rt.usize, 2, 2) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.DANGLING, rt.isizeSub(rt.u8Rem(n, 3) as rt.isize, 1), 8, 8) === rt.NULL ? 1 : 0); - rt.byteVecPush(out, rt.sliceAsPtr({ v: rt.ptrSlice(rt.NULL, 0) }.v) === rt.NULL ? 1 : 0); - rt.byteVecPush(out, rt.byteSliceAsPtr({ v: rt.ptrByteSlice(rt.NULL, 0, false) }.v) === rt.NULL ? 1 : 0); - rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr({ v: rt.ptrSlice(rt.DANGLING, 0) }.v), -1, 2, 2) === rt.NULL ? 1 : 0); - rt.byteVecPush(out, rt.sliceAsPtr({ v: rt.ptrSlice(rt.NULL, 0) }.v) === rt.NULL ? 1 : 0); - rt.byteVecPush(out, rt.byteSliceAsPtr({ v: rt.ptrByteSlice(rt.NULL, 0, false) }.v) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, new rt.RawSlice(rt.NULL, 0).ptr === rt.NULL ? 1 : 0); + rt.byteVecPush(out, new rt.RawSlice(rt.NULL, 0).ptr === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrWrappingAdd(new rt.RawSlice(rt.DANGLING, 0).ptr, -1, 2, 2) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, new rt.RawSlice(rt.NULL, 0).ptr === rt.NULL ? 1 : 0); + rt.byteVecPush(out, new rt.RawSlice(rt.NULL, 0).ptr === rt.NULL ? 1 : 0); const none2: rt.ByteSlice = rt.ptrByteSlice(rt.DANGLING, 0, true); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.byteSliceAsPtr(none2), -1, 1, 1) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.byteSliceAsPtr(rt.byteSliceRange(none2, 0, 0)), -1, 1, 1) === rt.NULL ? 1 : 0); const wide2: rt.u16[] = rt.ptrSlice(rt.DANGLING, 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr(rt.sliceRange(wide2, 0, 0)), -1, 2, 2) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr(rt.optionUnwrap(rt.sliceGetRange(wide2, 0, 0))), -1, 2, 2) === rt.NULL ? 1 : 0); - rt.byteVecPush(out, rt.sliceAsPtr({ v: rt.NULL }.v) === rt.NULL ? 1 : 0); + rt.byteVecPush(out, new rt.RawSlice(rt.sliceAsPtr(rt.NULL), 3).ptr === rt.NULL ? 1 : 0); const row: rt.u32[] = [n as rt.u32, 9, 10]; - const unsizedLanes: rt.Place = { v: row }; - rt.byteVecPush(out, rt.sliceAsPtr(unsizedLanes.v) === rt.NULL ? 1 : 0); - rt.byteVecPush(out, rt.ptrAdd(rt.sliceAsPtr(unsizedLanes.v), 2).v & 0xff); - rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr({ v: rt.ptrSlice(rt.ptrWrappingAdd(rt.NULL, rt.usizeAdd(rt.u8Rem(n, 3) as rt.usize, 1), 4, 4), 0) }.v), -rt.usizeAdd(rt.u8Rem(n, 3) as rt.usize, 1), 4, 4) === rt.NULL ? 1 : 0); + const unsizedLanes: rt.RawSlice = new rt.RawSlice(rt.sliceAsPtr(row), 3); + rt.byteVecPush(out, unsizedLanes.ptr === rt.NULL ? 1 : 0); + rt.byteVecPush(out, rt.ptrAdd(unsizedLanes.ptr, 2).v & 0xff); + rt.byteVecPush(out, rt.ptrWrappingAdd(new rt.RawSlice(rt.ptrWrappingAdd(rt.NULL, rt.usizeAdd(rt.u8Rem(n, 3) as rt.usize, 1), 4, 4), 0).ptr, -rt.usizeAdd(rt.u8Rem(n, 3) as rt.usize, 1), 4, 4) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.sliceAsPtr(rt.NULL) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.byteSliceAsPtr(rt.NULL), n as rt.usize, 1, 1) === rt.NULL ? 1 : 0); rt.byteVecPush(out, rt.ptrWrappingAdd(rt.sliceAsPtr(rt.DANGLING), -1, 2, 2) === rt.NULL ? 1 : 0); } } +/** + * Raw pointers to slices, each where it starts and how long it is: read and written + * through, whole and in part, of bytes too, made longer than what is there and never + * read past it, `NonNull`'s, a boxed slice's, and null's. + */ +export function rawSlices(data: rt.ByteSlice, out: rt.ByteVec): void { + const n: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + const v: rt.u32[] = rt.iterToArray(rt.iterChain(rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (_2: rt.u8): rt.u32 => { + return _2 as rt.u32; + }), rt.iterSlice([5, 6, 7])), undefined, 4, true); + const len: rt.usize = v.length; + const p: rt.RawSlice = new rt.RawSlice(rt.sliceAsPtr(v), len); + rt.idx(p, 0); + const _19: rt.u32 = rt.u32Add(rt.rawSliceDeref(p)[0], 1); + rt.rawSliceDeref(p)[0] = _19; + const _20: rt.u32 = n as rt.u32; + const _21: rt.usize = rt.usizeSub(len, 1); + rt.idx(p, _21); + rt.rawSliceDeref(p)[_21] = _20; + rt.byteVecPush(out, rt.rawSliceDeref(p).length & 0xff); + rt.byteVecPush(out, p.length & 0xff); + rt.idx(p, 1); + rt.byteVecPush(out, rt.rawSliceDeref(p)[1] & 0xff); + const tail: rt.u32[] = rt.sliceRangeMut(rt.rawSliceDeref(p), 1); + rt.idx(tail, 0); + tail[0] = 9; + rt.byteVecPush(out, rt.iterSum(rt.iterSlice(rt.rawSliceDeref(p)), rt.u32Add, 0) & 0xff); + emit(v, out); + const bytes: rt.ByteVec = rt.byteSliceToByteVec(data); + rt.byteVecPush(bytes, 3); + const b: rt.RawSlice = new rt.RawSlice(rt.byteVecAsPtr(bytes), bytes.length); + rt.idx(b, 0); + const slice2 = rt.rawByteSliceDeref(b, false); + const slice3 = rt.rawByteSliceDeref(b, false); + slice3.data[slice3.start] = slice2.data[slice2.start] ^ 255; + const _66: rt.usize = rt.usizeSub(rt.rawByteSliceDeref(b, false).length, 1); + rt.idx(b, _66); + const slice4 = rt.rawByteSliceDeref(b, false); + rt.byteVecPush(out, slice4.data[slice4.start + _66]); + rt.byteVecPush(out, rt.byteSliceRange(rt.rawByteSliceDeref(b, false), 1).length & 0xff); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteVecView(bytes)), undefined, true); + const short: rt.u32[] = [n as rt.u32, 1]; + const long: rt.RawSlice = new rt.RawSlice(rt.sliceAsPtr(short), 10); + rt.byteVecPush(out, long.length & 0xff); + rt.byteVecPush(out, rt.ptrAdd(long.ptr, 1).v & 0xff); + const pointer = rt.sliceAsPtr(v); + const nn: rt.RawSlice = new rt.RawSlice(rt.optionUnwrap(pointer === rt.NULL ? undefined : pointer), 2); + rt.byteVecPush(out, nn.length & 0xff); + const _102: rt.RawSlice = nn; + rt.idx(_102, 1); + rt.rawSliceDeref(_102)[1] = 11; + const _109: rt.u32[] = rt.rawSliceDeref(nn); + rt.idx(_109, 1); + rt.byteVecPush(out, _109[1] & 0xff); + const pointer2 = nn; + rt.byteVecPush(out, option_mapOr$0grxkdu(pointer2.ptr === rt.NULL ? undefined : rt.rawSliceDeref(pointer2), 0, (s: rt.u32[]): rt.u32 => { + rt.idx(s, 0); + return s[0]; + }) & 0xff); + const null_: rt.RawSlice = new rt.RawSlice(rt.NULL, 0); + const pointer3 = null_; + rt.byteVecPush(out, ((pointer3.ptr === rt.NULL ? undefined : pointer3)) === undefined ? 1 : 0); + const pointer4 = null_; + rt.byteVecPush(out, ((pointer4.ptr === rt.NULL ? undefined : rt.rawSliceDeref(pointer4))) === undefined ? 1 : 0); + const raw: rt.RawSlice = rt.rawSliceOf([n as rt.u32, 2, 3]); + rt.idx(raw, 2); + rt.rawSliceDeref(raw)[2] = 8; + emit(rt.rawSliceDeref(raw), out); + const whole: rt.RawSlice = new rt.RawSlice(rt.sliceAsPtr(short), 2); + const ofRef: rt.RawSlice = rt.rawSliceOf(rt.sliceRange(v, 1, 3)); + rt.byteVecPush(out, rt.usizeAdd(whole.length, ofRef.length) & 0xff); + rt.idx(ofRef, 1); + rt.byteVecPush(out, rt.rawSliceDeref(ofRef)[1] & 0xff); +} + +/** + * Empty slices made from pointers into no array, a dangling one and one off null, each + * part of which keeps the pointer as Rust's does: after moving back to address 0 it is null. + */ +export function emptyParts(data: rt.ByteSlice, out: rt.ByteVec): void { + const _130: (a0: rt.Place) => rt.u8 = (p: rt.Place): rt.u8 => { + return rt.ptrWrappingAdd(p, -1, 1, 1) === rt.NULL ? 1 : 0; + }; + const k: rt.usize = rt.usizeAdd(rt.u8Rem(option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0), 4) as rt.usize, 1); + const wide: rt.Place = rt.DANGLING; + const off: rt.Place = rt.ptrWrappingAdd(rt.NULL, k, 2, 2); + const iter: rt.Iter<[rt.u16[], rt.usize]> = rt.iterSlice([[rt.ptrSlice(wide, 0), 1] as [rt.u16[], rt.usize], [rt.ptrSlice(off, 0), k] as [rt.u16[], rt.usize]]); + while (true) { + const _23: [rt.u16[], rt.usize] | rt.Done = iter.next(); + if (_23 === rt.DONE) { + break; + } + const back: rt.usize = _23[1]; + const null_: (a0: rt.Place) => rt.u8 = (p2: rt.Place): rt.u8 => { + return rt.ptrWrappingAdd(p2, -back, 2, 2) === rt.NULL ? 1 : 0; + }; + const _32: [rt.u16[], rt.u16[]] = rt.sliceSplitAt(_23[0], 0); + const part = rt.sliceAsPtr(_32[0]); + const _35: rt.u8 = null_(part); + const part2 = rt.sliceAsPtr(_32[1]); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(_35, null_(part2))), undefined, true); + const _45: [rt.u16[], rt.u16[]] = rt.optionUnwrap(rt.sliceSplitAtChecked(_23[0], 0)); + const part3 = rt.sliceAsPtr(_45[0]); + const _49: rt.u8 = null_(part3); + const part4 = rt.sliceAsPtr(_45[1]); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(_49, null_(part4))), undefined, true); + const part5 = rt.sliceAsPtr(rt.partsRemainder(rt.sliceChunksExact(_23[0], 2))); + rt.byteVecPush(out, null_(part5)); + const part6 = rt.sliceAsPtr(rt.partsRemainder(rt.sliceRchunksExact(_23[0], 3))); + rt.byteVecPush(out, null_(part6)); + rt.iterExtendBytes(out, rt.iterMap(rt.sliceSplit(_23[0], (_2: rt.u16): boolean => { + return _2 === 0; + }), (part7: rt.u16[]): rt.u8 => { + return null_(rt.sliceAsPtr(part7)); + }), "next"); + rt.iterExtendBytes(out, rt.iterMap(rt.sliceRsplit(_23[0], (_22: rt.u16): boolean => { + return _22 === 0; + }), (part9: rt.u16[]): rt.u8 => { + return null_(rt.sliceAsPtr(part9)); + }), "next"); + rt.iterExtendBytes(out, rt.iterMap(rt.sliceSplitn(_23[0], 2, (_24: rt.u16): boolean => { + return _24 === 0; + }), (part11: rt.u16[]): rt.u8 => { + return null_(rt.sliceAsPtr(part11)); + }), "next"); + const part13 = rt.sliceAsPtr(rt.optionUnwrap(rt.sliceGetRange(_23[0], 0, 0))); + rt.byteVecPush(out, null_(part13)); + const part14 = rt.sliceAsPtr(rt.optionUnwrap(rt.sliceLastChunk(_23[0], 0))); + rt.byteVecPush(out, null_(part14)); + const part15 = rt.sliceAsPtr(rt.optionUnwrap(rt.sliceGetRange(_23[0], 0))); + rt.byteVecPush(out, null_(part15)); + const part16 = rt.sliceAsPtr(rt.sliceRange(_23[0], 0, 0)); + rt.byteVecPush(out, null_(part16)); + rt.byteVecPush(out, rt.sliceSplitFirst(_23[0]) === undefined ? 1 : 0); + rt.byteVecPush(out, rt.iterCount(rt.sliceChunks(_23[0], 2)) & 0xff); + } + const byte: rt.Place = rt.DANGLING; + const s: rt.ByteSlice = rt.ptrByteSlice(byte, 0, false); + const part17 = rt.byteSliceAsPtr(s); + rt.byteVecPush(out, _130(part17)); + const _139: [rt.ByteSlice, rt.ByteSlice] = rt.byteSliceSplitAt(s, 0); + const part18 = rt.byteSliceAsPtr(_139[0]); + const _142: rt.u8 = _130(part18); + const part19 = rt.byteSliceAsPtr(_139[1]); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(_142, _130(part19))), undefined, true); + const part20 = rt.byteSliceAsPtr(rt.byteSliceRange(s, 0)); + rt.byteVecPush(out, _130(part20)); + const part21 = rt.byteSliceAsPtr(rt.optionUnwrap(rt.iterToOption(rt.byteSliceSplit(s, (_25: rt.u8): boolean => { + return _25 === 0; + }).next()))); + rt.byteVecPush(out, _130(part21)); + const raw: rt.RawSlice = new rt.RawSlice(off, 0); + rt.byteVecPush(out, raw.length & 0xff); + rt.byteVecPush(out, rt.ptrWrappingAdd(raw.ptr, -k, 2, 2) === rt.NULL ? 1 : 0); + const part22 = rt.ptrWrappingAdd(new rt.RawSlice(byte, 0).ptr, 0, 1, 1); + rt.byteVecPush(out, _130(part22)); +} + export function emit(values: rt.u32[], out: rt.ByteVec): void { rt.iterExtendBytes(out, rt.iterFlatMap(rt.iterSlice(values), (x: rt.u32): Uint8Array => { return rt.u32ToBeBytes(x).reverse(); diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index c41a03df1..62a81e737 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -24,6 +24,7 @@ import { defaults as defaults_defaults } from "./defaults.ts"; import { durations } from "./durations.ts"; import { dyns, shared as dyns_shared, anys, fns } from "./dyns.ts"; import { edits as edits_edits, bytes as edits_bytes } from "./edits.ts"; +import { vecs as elements_vecs, arrays as elements_arrays, segments as elements_segments, inline, edges as elements_edges, rawSlices, emptyParts } from "./elements.ts"; import { eqs } from "./errors.ts"; import { fields as fields_fields } from "./fields.ts"; import { floats, mulAdds as floats_mulAdds, formats as floats_formats, parses as floats_parses } from "./floats.ts"; @@ -45,7 +46,7 @@ import { u8s as methods_u8s, u16s as methods_u16s, u32s as methods_u32s, u64s as import { nested as nested_nested } from "./nested.ts"; import { objects } from "./objects.ts"; import { orders, bytes as orders_bytes, tuples as orders_tuples, held as orders_held, btrees } from "./orders.ts"; -import { lists as pointers_lists, shared as pointers_shared, places as pointers_places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound as pointers_rebound, lapsed, writers, made as pointers_made, segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken as pointers_taken, bytes as pointers_bytes, loaded, reattached, partWritten, aimed } from "./pointers.ts"; +import { lists as pointers_lists, shared as pointers_shared, places as pointers_places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound as pointers_rebound, lapsed, writers, made as pointers_made, segments as pointers_segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken as pointers_taken, bytes as pointers_bytes, loaded, reattached, partWritten, aimed } from "./pointers.ts"; import { refs, toRefs, adapted as refs_adapted, compared } from "./refs.ts"; import { results as results_results } from "./results.ts"; import { returns } from "./returns.ts"; @@ -225,7 +226,7 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::slices", cows_slices as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::bytes", cows_bytes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u8s", wraps_u8s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u16s", wraps_u16s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u32s", wraps_u32s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u64s", wraps_u64s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::u128s", wraps_u128s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::usizes", wraps_usizes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i8s", wraps_i8s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i16s", wraps_i16s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i32s", wraps_i32s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i64s", wraps_i64s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::i128s", wraps_i128s as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["wraps::isizes", wraps_isizes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::enums", (_2174: rt.u8, data74: rt.ByteSlice, out75: rt.ByteVec): undefined => { enums(data74, out75); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::segments", (_2175: rt.u8, data75: rt.ByteSlice, out76: rt.ByteVec): undefined => { - segments(data75, out76); + pointers_segments(data75, out76); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::uninit_runs", (_2176: rt.u8, data76: rt.ByteSlice, out77: rt.ByteVec): undefined => { uninitRuns(data76, out77); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::beside_written", (_2177: rt.u8, data77: rt.ByteSlice, out78: rt.ByteVec): undefined => { @@ -280,8 +281,22 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) downgraded(data97, out98); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["borrows::leaks", (_2198: rt.u8, data98: rt.ByteSlice, out99: rt.ByteVec): undefined => { leaks(data98, out99); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::own", (_2199: rt.u8, data99: rt.ByteSlice, out100: rt.ByteVec): undefined => { - hashes_own(data99, out100); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::vecs", (_2199: rt.u8, data99: rt.ByteSlice, out100: rt.ByteVec): undefined => { + elements_vecs(data99, out100); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::arrays", (_2200: rt.u8, data100: rt.ByteSlice, out101: rt.ByteVec): undefined => { + elements_arrays(data100, out101); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::segments", (_2201: rt.u8, data101: rt.ByteSlice, out102: rt.ByteVec): undefined => { + elements_segments(data101, out102); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::inline", (_2202: rt.u8, data102: rt.ByteSlice, out103: rt.ByteVec): undefined => { + inline(data102, out103); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::edges", (_2203: rt.u8, data103: rt.ByteSlice, out104: rt.ByteVec): undefined => { + elements_edges(data103, out104); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::raw_slices", (_2204: rt.u8, data104: rt.ByteSlice, out105: rt.ByteVec): undefined => { + rawSlices(data104, out105); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::empty_parts", (_2205: rt.u8, data105: rt.ByteSlice, out106: rt.ByteVec): undefined => { + emptyParts(data105, out106); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::own", (_2206: rt.u8, data106: rt.ByteSlice, out107: rt.ByteVec): undefined => { + hashes_own(data106, out107); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["refs::compared", (a0159: rt.u8, a1159: rt.ByteSlice, a2159: rt.ByteVec): undefined => { compared(a0159, a1159.data, a1159.start, a1159.length, a2159); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 8608f3961..1f6e3a0db 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -402,6 +402,102 @@ pub fn edges(data: &[u8], out: &mut Vec) { } } +/// Raw pointers to slices, each where it starts and how long it is: read and written +/// through, whole and in part, of bytes too, made longer than what is there and never +/// read past it, `NonNull`'s, a boxed slice's, and null's. +pub fn raw_slices(data: &[u8], out: &mut Vec) { + let n = data.first().copied().unwrap_or(0); + let mut v: Vec = data.iter().map(|&b| u32::from(b)).chain([5, 6, 7]).collect(); + let len = v.len(); + let p = ptr::slice_from_raw_parts_mut(v.as_mut_ptr(), len); + unsafe { + (*p)[0] += 1; + (*p)[len - 1] = u32::from(n); + out.push((&*p).len() as u8); + out.push(p.len() as u8); + out.push((*p)[1] as u8); + let tail = &mut (&mut *p)[1..]; + tail[0] = 9; + out.push((&*p).iter().sum::() as u8); + } + emit(&v, out); + let mut bytes = data.to_vec(); + bytes.push(3); + let b = ptr::slice_from_raw_parts_mut(bytes.as_mut_ptr(), bytes.len()); + unsafe { + (*b)[0] ^= 0xff; + out.push((*b)[(&*b).len() - 1]); + out.push((&*b)[1..].len() as u8); + } + out.extend(&bytes); + // Longer than its array, which nothing reads past. + let short = [u32::from(n), 1]; + let long = ptr::slice_from_raw_parts(short.as_ptr(), 10); + out.push(long.len() as u8); + out.push(unsafe { (long as *const u32).add(1).read() } as u8); + let nn = NonNull::slice_from_raw_parts(NonNull::new(v.as_mut_ptr()).unwrap(), 2); + out.push(nn.len() as u8); + unsafe { + (*nn.as_ptr())[1] = 11; + out.push(nn.as_ref()[1] as u8); + out.push(nn.as_ptr().as_mut().map_or(0, |s| s[0]) as u8); + } + let null = ptr::slice_from_raw_parts_mut(ptr::null_mut::(), 0); + out.push(NonNull::new(null).is_none() as u8); + out.push(unsafe { null.as_ref() }.is_none() as u8); + let boxed: Box<[u32]> = vec![u32::from(n), 2, 3].into_boxed_slice(); + let raw = Box::into_raw(boxed); + unsafe { (*raw)[2] = 8 }; + let back = unsafe { Box::from_raw(raw) }; + emit(&back, out); + let whole: *const [u32] = &raw const short; + let of_ref: *const [u32] = &v[1..3]; + out.push((whole.len() + of_ref.len()) as u8); + out.push(unsafe { (*of_ref)[1] } as u8); +} + +/// Empty slices made from pointers into no array, a dangling one and one off null, each +/// part of which keeps the pointer as Rust's does: after moving back to address 0 it is null. +pub fn empty_parts(data: &[u8], out: &mut Vec) { + let k = usize::from(data.first().copied().unwrap_or(0) % 4) + 1; + let wide = NonNull::::dangling().as_ptr(); + let off = ptr::null::().wrapping_add(k); + for (s, back) in [ + (unsafe { slice::from_raw_parts(wide, 0) }, 1), + (unsafe { slice::from_raw_parts(off, 0) }, k), + ] { + let null = |p: *const u16| p.wrapping_sub(back).is_null() as u8; + let (front, rest) = s.split_at(0); + out.extend([null(front.as_ptr()), null(rest.as_ptr())]); + let (front, rest) = s.split_at_checked(0).unwrap(); + out.extend([null(front.as_ptr()), null(rest.as_ptr())]); + out.push(null(s.chunks_exact(2).remainder().as_ptr())); + out.push(null(s.rchunks_exact(3).remainder().as_ptr())); + out.extend(s.split(|&x| x == 0).map(|part| null(part.as_ptr()))); + out.extend(s.rsplit(|&x| x == 0).map(|part| null(part.as_ptr()))); + out.extend(s.splitn(2, |&x| x == 0).map(|part| null(part.as_ptr()))); + out.push(null(s.first_chunk::<0>().unwrap().as_ptr())); + out.push(null(s.last_chunk::<0>().unwrap().as_ptr())); + out.push(null(s.get(..).unwrap().as_ptr())); + out.push(null(s[..0].as_ptr())); + out.push(s.split_first().is_none() as u8); + out.push(s.chunks(2).count() as u8); + } + let byte = NonNull::::dangling().as_ptr(); + let s = unsafe { slice::from_raw_parts_mut(byte, 0) }; + let null = |p: *const u8| p.wrapping_sub(1).is_null() as u8; + out.push(null(s.as_ptr())); + let (front, rest) = s.split_at_mut(0); + out.extend([null(front.as_ptr()), null(rest.as_ptr())]); + out.push(null(s[..].as_ptr())); + out.push(null(s.split(|&x| x == 0).next().unwrap().as_ptr())); + // A raw pointer to an empty slice, its length, and its start. + let raw = ptr::slice_from_raw_parts(off, 0); + out.push(raw.len() as u8); + out.push((raw as *const u16).wrapping_sub(k).is_null() as u8); + out.push(null((ptr::slice_from_raw_parts(byte, 0) as *const u8).wrapping_add(0))); +} + /// Functions of the crate's named as std's pointer functions. mod mine { pub mod ptr { diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 32698b932..24d7c3b5c 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -293,6 +293,8 @@ fn cases() -> Vec<(&'static str, Case)> { ("elements::segments", |_, data, out| elements::segments(data, out)), ("elements::inline", |_, data, out| elements::inline(data, out)), ("elements::edges", |_, data, out| elements::edges(data, out)), + ("elements::raw_slices", |_, data, out| elements::raw_slices(data, out)), + ("elements::empty_parts", |_, data, out| elements::empty_parts(data, out)), ("hashes::own", |_, data, out| hashes::own(data, out)), ("refs::compared", refs::compared), ] diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 84e2d396e..70a2a2b7e 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -325,7 +325,7 @@ export function ptrCopy(src: Place, dst: Place, n: number, copy?: (valu */ const EMPTY = /* @__PURE__ */ new WeakMap>(); -/** `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` elements from `p`. */ +/** `slice::from_raw_parts` of `len` elements from `p`. */ export function ptrSlice(p: Place, len: number): T[] { // None from a pointer into no array, such as null's or a dangling one's, keeps it. if (len === 0 && !(p instanceof AtIndex)) { @@ -340,9 +340,42 @@ export function ptrSlice(p: Place, len: number): T[] { } /** - * `slice::from_raw_parts` and `ptr::slice_from_raw_parts` of `len` bytes from `p`. Only a - * `shared` reference to bytes in no array of bytes may be a copy of them, since nothing - * writes them while it lives. + * A raw pointer to a slice, `*const [T]`, `*mut [T]`, or `NonNull<[T]>`: where its first + * element is and how many it spans, which nothing checks until a read through it, as in Rust. + */ +export class RawSlice { + readonly ptr: Place; + readonly length: number; + + constructor(ptr: Place, length: number) { + this.ptr = ptr; + this.length = length; + } +} + +/** The raw pointer to `s`, a slice, a `[T; N]`, or a `Vec`'s storage. */ +export function rawSliceOf(s: { readonly length: number; [i: number]: T }): RawSlice { + return new RawSlice(sliceAsPtr(s), s.length); +} + +/** The raw pointer to the bytes of `s`. */ +export function rawByteSliceOf(s: ByteSlice): RawSlice { + return new RawSlice(byteSliceAsPtr(s), s.length); +} + +/** The slice `p` points at, read through it, which must be there. */ +export function rawSliceDeref(p: RawSlice): T[] { + return ptrSlice(p.ptr, p.length); +} + +/** The bytes `p` points at, read through it, `shared` as `ptrByteSlice` takes it. */ +export function rawByteSliceDeref(p: RawSlice, shared: boolean): ByteSlice { + return ptrByteSlice(p.ptr, p.length, shared); +} + +/** + * `slice::from_raw_parts` of `len` bytes from `p`. Only a `shared` reference to bytes in + * no array of bytes may be a copy of them, since nothing writes them while it lives. */ export function ptrByteSlice(p: Place, len: number, shared: boolean): ByteSlice { if (len === 0 && !(p instanceof AtIndex)) { @@ -514,7 +547,14 @@ export function rangeCheck(length: number, start: number, end: number): void { /** `&s[start..end]`, with `end` defaulting to the length. */ export function sliceRange(s: T[], start: number, end: number = s.length): T[] { rangeCheck(s.length, start, end); - // An empty slice's part is it, which keeps the pointer it was made from. + return slicePart(s, start, end); +} + +/** + * Elements `start` to `end` of `s`, a copy as a shared part is, or of an empty slice the + * slice itself, which keeps the pointer it was made from as Rust's part does. + */ +export function slicePart(s: T[], start: number, end: number = s.length): T[] { return s.length === 0 ? s : s.slice(start, end); } @@ -604,7 +644,7 @@ export function sliceRangeMut(s: T[], start: number, end: number = s.length): /** `get` of `start..end`: the part, or `undefined` unless it is within the slice. */ export function sliceGetRange(s: T[], start: number, end: number = s.length): T[] | undefined { - return start <= end && end <= s.length ? (s.length === 0 ? s : s.slice(start, end)) : undefined; + return start <= end && end <= s.length ? slicePart(s, start, end) : undefined; } /** `get_mut` of `start..end`: the runtime's view of it, or `undefined` as `sliceGetRange`. */ @@ -614,7 +654,7 @@ export function sliceGetRangeMut(s: T[], start: number, end: number = s.lengt /** `last_chunk`: the last `n` elements, or `undefined` if fewer. */ export function sliceLastChunk(s: T[], n: number): T[] | undefined { - return n > s.length ? undefined : s.slice(s.length - n); + return n > s.length ? undefined : slicePart(s, s.length - n); } /** `last_chunk_mut`: a view of the last `n` elements, or `undefined` if fewer. */ @@ -640,12 +680,12 @@ export function sliceSplitFirst(s: T[]): [T, T[]] | undefined { /** `split_at`, panicking past the length. */ export function sliceSplitAt(s: T[], n: number): [T[], T[]] { if (n > s.length) panic("mid > len"); - return [s.slice(0, n), s.slice(n)]; + return [slicePart(s, 0, n), slicePart(s, n)]; } /** `split_at_checked`: the halves, or `undefined` past the length. */ export function sliceSplitAtChecked(s: T[], n: number): [T[], T[]] | undefined { - return n > s.length ? undefined : [s.slice(0, n), s.slice(n)]; + return n > s.length ? undefined : [slicePart(s, 0, n), slicePart(s, n)]; } /** `split_at_mut`: views of either half, panicking past the length. */ diff --git a/ts/runtime/rt/iter.ts b/ts/runtime/rt/iter.ts index 6f1c6437e..3eaedf95d 100644 --- a/ts/runtime/rt/iter.ts +++ b/ts/runtime/rt/iter.ts @@ -22,7 +22,7 @@ // `owned` functions follow it, dispatching on the kind, so a bundle that never drops an // item carries none of it. -import { ByteSlice, ByteVec, byteVecExtendFromSlice, byteVecPush, byteVecReserve, byteVecWithCapacity, type Capped, capOf, capReserve, capSet, Err, type None, Ok, type Place, panic, place, type RangeBound, type Result, sliceView, vecCapacity, vecExtendFromSlice, vecMax, vecWithCapacity } from "../rt.ts"; +import { ByteSlice, ByteVec, byteVecExtendFromSlice, byteVecPush, byteVecReserve, byteVecWithCapacity, type Capped, capOf, capReserve, capSet, Err, type None, Ok, type Place, panic, place, type RangeBound, type Result, slicePart, sliceView, vecCapacity, vecExtendFromSlice, vecMax, vecWithCapacity } from "../rt.ts"; import { AtIndex, BTreeIter, HashIter, type Ordering } from "./collections.ts"; import { type Hasher, hash8 } from "./hash.ts"; import { charStep, intToBig, type Limbs, lt as wideLt, type Step } from "./int.ts"; @@ -471,7 +471,7 @@ function chunks(s: S, length: number, size: number, exact: boolean, rev: bool } function sliceCopy(s: T[], start: number, end: number): T[] { - return s.slice(start, end); + return slicePart(s, start, end); } function byteSlicePart(s: ByteSlice, start: number, end: number): ByteSlice { diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 0037ae0a7..a820404e3 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -105,6 +105,10 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { if byte && name != "from_raw_parts" && 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; + } out(o, Some(self.const_int(arg(1)?)?), "a slice") } ( diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 4debb5135..1d1546c5b 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -204,9 +204,20 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let glue = matches!(self.env, Env::Shim) && place.local.as_u32() == 1 && pty.ty == decls[place.local].ty; // A box's pointer is the box, which is its contents. - if self.b.t.place_pointee(ty).is_some() && !glue && !self.box_pointer(place.local) { + let boxed = self.box_pointer(place.local); + if self.b.t.place_pointee(ty).is_some() && !glue && !boxed { e = e.member("v"); } + // A raw pointer to a slice is where it starts, read through as the slice. + if let ty::RawPtr(pointee, _) = ty.kind() + && !glue && !boxed + && self.b.t.pointee(*pointee) == crate::lower::pointer::Pointee::Slice + { + e = match crate::lower::pointer::slice_of_bytes(*pointee) { + true => Expr::rt("rawByteSliceDeref", vec![e, Expr::lit("false")]), + false => Expr::rt("rawSliceDeref", vec![e]), + }; + } } ProjectionElem::Field(field, _) => { e = self.field(e, ty, pty.variant_index, field, span, out); diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 2d2904954..477f72fcb 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -77,6 +77,26 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let pointee = place.ty(&self.mir.local_decls, tcx).ty; match self.b.t.pointee(pointee) { Pointee::Object => self.place_expr(*place, span, out), + // `&raw mut *p` of a raw pointer is `p`; of a reference, the slice's start and + // length. + Pointee::Slice => match place.projection.as_slice() { + [rest @ .., mir::ProjectionElem::Deref] + if Place::ty_from(place.local, rest, &self.mir.local_decls, tcx).ty.is_raw_ptr() => + { + let base = Place { + local: place.local, + projection: tcx.mk_place_elems(rest), + }; + self.place_expr(base, span, out) + } + _ => { + let value = self.place_expr(*place, span, out); + match crate::lower::pointer::slice_of_bytes(pointee) { + true => Expr::rt("rawByteSliceOf", vec![value]), + false => Expr::rt("rawSliceOf", vec![value]), + } + } + }, // A slice of all of an array's bytes, as a borrow of it is. Pointee::Bytes => { let value = self.place_expr(*place, span, out); @@ -236,6 +256,22 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } /// The integer an operand has, if it is a constant one, a named `const` included. + /// `p`, a raw pointer to an array, `from`, as one to a slice of all of it: its first + /// element's pointer and the array's length. + fn raw_unsize(&mut self, p: Expr, from: Ty<'tcx>, span: Span) -> Expr { + let Some(array) = from.builtin_deref(true) else { + return self.err(span, format!("a coercion of `{from}` to a pointer to a slice")); + }; + let ty::Array(elem, len) = *array.kind() else { + return self.err(span, format!("a coercion of `{from}` to a pointer to a slice")); + }; + let Some(len) = len.try_to_target_usize(self.tcx) else { + return self.err(span, format!("a coercion of `{from}`, of no fixed length, to a pointer to a slice")); + }; + let first = crate::lower::pointer::cast(self.b, p, array, elem, span); + Expr::New(Box::new(Expr::Rt("RawSlice".into())), vec![first, Expr::lit(len.to_string())]) + } + pub(super) fn const_int(&self, op: &Operand<'tcx>) -> Option { let Operand::Constant(c) = op else { return None; @@ -332,22 +368,18 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let value = self.operand(op, span, out); match crate::lower::bytes_ref(from) { true => value, - // A raw pointer to a slice is an `rt.Place` of one, which one to an array is not. - false if from.is_raw_ptr() => { - self.err(span, format!("a coercion of `{from}` to `{to}`, a pointer to a slice")) - } + false if from.is_raw_ptr() => self.raw_unsize(value, from, span), false => Expr::rt("byteSliceFrom", vec![value]), } } _ => self.err(span, format!("a coercion to `{to}`")), } } - // A raw pointer to a slice is an `rt.Place` of it; one to an array is the array. PointerCoercion::Unsize if from.is_raw_ptr() && to.builtin_deref(true).is_some_and(|t| t.is_slice()) => { let value = self.operand(op, span, out); - Expr::Object(vec![(Key::Name("v".into()), value)]) + self.raw_unsize(value, from, span) } PointerCoercion::Unsize | PointerCoercion::ReifyFnPointer(_) diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 62adb5c1f..76a37e88e 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1306,7 +1306,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { // A place, unless a pointer to what it holds is that object. Some(library::Std::MaybeUninit) => match self.pointee(args.type_at(0)) { pointer::Pointee::Object | pointer::Pointee::Bytes => self.object_kind(args.type_at(0)), - pointer::Pointee::Place => None, + pointer::Pointee::Place | pointer::Pointee::Slice => None, }, Some(_) => None, None if adt.is_struct() || adt.is_union() => Some(ObjectKind::Class), diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 0a893fbc0..08a8decfe 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -47,6 +47,9 @@ pub enum Pointee { /// An `rt.ByteSlice` of the `[u8; N]` it points at, as a reference to one is, which /// compares by where its bytes are. Bytes, + /// An `rt.RawSlice` of a `[T]`: the pointer to its first element and its length, which + /// a read through it makes the slice of. + Slice, } impl<'a, 'tcx> Translator<'a, 'tcx> { @@ -60,6 +63,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if super::byte_array(ty) { return Pointee::Bytes; } + if ty.is_slice() { + return Pointee::Slice; + } let object = match (self.object_kind(ty), ty.kind()) { (Some(ObjectKind::Array | ObjectKind::Bytes), ty::Array(..) | ty::Tuple(_)) => true, // An async body passes its context as a pointer to it, and a waker's vtable is @@ -102,6 +108,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Pointee::Place => Some(format!( "comparing pointers to `{ty}`, each a place JS makes of its own" )), + Pointee::Slice => Some(format!( + "comparing pointers to `{ty}`, whose element pointers are places JS makes of their own" + )), } } @@ -140,6 +149,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Pointee::Place => Type::Rt("Place", vec![self.ty(pointee, span)]), Pointee::Object => self.ty(pointee, span), Pointee::Bytes => Type::Rt("ByteSlice", Vec::new()), + Pointee::Slice => match pointee.kind() { + ty::Slice(elem) => Type::Rt("RawSlice", vec![self.ty(*elem, span)]), + _ => unreachable!("a slice's pointee"), + }, } } @@ -193,7 +206,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { match (elem.kind(), self.pointee(*elem)) { (ty::Uint(ty::UintTy::U8), _) => Expr::New(Box::new(Expr::ident("Uint8Array")), vec![len]), (_, Pointee::Place) => Expr::New(Box::new(Expr::ident("Array")), vec![len]), - (_, Pointee::Object | Pointee::Bytes) => { + (_, Pointee::Object | Pointee::Bytes | Pointee::Slice) => { let elem_ty = self.ty(*elem, span); let each = Expr::Arrow( Vec::new(), @@ -433,6 +446,16 @@ pub fn call<'tcx>( let bytes = |b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>| b.t.pointee(ty) == Pointee::Bytes; let to_pointer = |e: Expr| Expr::rt("byteSliceFrom", vec![e]); let to_reference = |e: Expr| Expr::rt("byteSliceView", vec![e]); + // A raw pointer to a slice, from a reference to one, and the slice a read through it + // makes, shared if nothing writes through it. + let raw_of = |ty: Ty<'tcx>, s: Expr| match slice_of_bytes(ty) { + true => Expr::rt("rawByteSliceOf", vec![s]), + false => Expr::rt("rawSliceOf", vec![s]), + }; + let deref = |ty: Ty<'tcx>, p: Expr, shared: bool| match slice_of_bytes(ty) { + true => Expr::rt("rawByteSliceDeref", vec![p, Expr::lit(shared.to_string())]), + false => Expr::rt("rawSliceDeref", vec![p]), + }; let mut args = args.into_iter(); let mut arg = || args.next().expect("an argument"); let e = match (owner, name) { @@ -478,14 +501,13 @@ pub fn call<'tcx>( ("ptr" | "NonNull", "addr" | "expose_provenance" | "with_addr" | "map_addr" | "without_provenance") => { b.err(span, "a pointer's address, which JS has no counterpart of") } - // A raw pointer to a slice is an `rt.Place` of it, null where its data pointer is. - ("ptr", "is_null") if let ty::Slice(elem) = pointee().kind() => { - let f = match elem.kind() { - ty::Uint(ty::UintTy::U8) => "byteSliceAsPtr", - _ => "sliceAsPtr", - }; - Expr::rt(f, vec![arg().member("v")]).binary("===", Expr::Rt("NULL".into())) + // A raw pointer to a slice is null where its first element's is. + ("ptr", "is_null") if kind(b, pointee()) == Some(Pointee::Slice) => { + arg().member("ptr").binary("===", Expr::Rt("NULL".into())) } + // Only a pointer to a slice has these, generic over its element. + ("ptr" | "NonNull", "len") => arg().member("length"), + ("ptr" | "NonNull", "is_empty") => arg().member("length").binary("===", Expr::lit("0")), ("ptr", "is_null") => arg().binary("===", Expr::Rt("NULL".into())), ("NonNull", "as_mut") if bytes(b, pointee()) => to_reference(arg()), // The same pointer, as another type of the same pointee. @@ -507,10 +529,12 @@ pub fn call<'tcx>( } // A reference to a `MaybeUninit` of bytes is the `Uint8Array`. (Some(Pointee::Bytes), _) if !super::bytes_ref(from) => to_pointer(arg()), + (Some(Pointee::Slice), _) => raw_of(ty, arg()), _ => arg(), } } // As `NonNull::from` makes one, of a `[u8; N]` given as the view of its bytes. + ("ptr", "from_ref" | "from_mut") if kind(b, pointee()) == Some(Pointee::Slice) => raw_of(pointee(), arg()), ("ptr", "from_ref") => match kind(b, pointee()) { Some(Pointee::Place) => Expr::Object(vec![(crate::ts::Key::Name("v".into()), arg())]), Some(Pointee::Bytes) => to_pointer(arg()), @@ -518,12 +542,27 @@ pub fn call<'tcx>( }, ("NonNull", "new") => { let p = b.spill(arg(), "pointer", out); + let at = match kind(b, pointee()) { + Some(Pointee::Slice) => p.clone().member("ptr"), + _ => p.clone(), + }; Expr::Cond( - Box::new(p.clone().binary("===", Expr::Rt("NULL".into()))), + Box::new(at.binary("===", Expr::Rt("NULL".into()))), Box::new(Expr::undefined()), Box::new(p), ) } + ("ptr", "as_ref" | "as_mut") if kind(b, pointee()) == Some(Pointee::Slice) => { + let p = b.spill(arg(), "pointer", out); + Expr::Cond( + Box::new(p.clone().member("ptr").binary("===", Expr::Rt("NULL".into()))), + Box::new(Expr::undefined()), + Box::new(deref(pointee(), p, name == "as_ref")), + ) + } + ("NonNull", "as_ref" | "as_mut") if kind(b, pointee()) == Some(Pointee::Slice) => { + deref(pointee(), arg(), name == "as_ref") + } // `Option<&T>`: a shared reference to a value JS copies is the value. ("ptr", "as_ref" | "as_mut") => { let p = b.spill(arg(), "pointer", out); @@ -546,11 +585,13 @@ pub fn call<'tcx>( }, // A pointer to what a box held, which no box holds any longer. ("Box", "into_raw" | "leak") => match kind(b, pointee()) { + Some(Pointee::Slice) if name == "into_raw" => raw_of(pointee(), arg()), Some(Pointee::Place) => Expr::Object(vec![(crate::ts::Key::Name("v".into()), arg())]), Some(Pointee::Bytes) if name == "into_raw" => to_pointer(arg()), _ => arg(), }, ("Box", "from_raw") => match kind(b, pointee()) { + Some(Pointee::Slice) => deref(pointee(), arg(), false), Some(Pointee::Place) => arg().member("v"), Some(Pointee::Bytes) => to_reference(arg()), _ => arg(), @@ -592,6 +633,7 @@ pub fn call<'tcx>( let p = arg(); let value = match kind(b, pointee()) { Some(Pointee::Place) => p.member("v"), + Some(Pointee::Slice) => deref(pointee(), p, false), _ => p, }; b.t.drop_value(value, pointee(), span).unwrap_or_else(Expr::undefined) @@ -723,7 +765,7 @@ pub fn call<'tcx>( | ("ptr", "slice_from_raw_parts" | "slice_from_raw_parts_mut") | ("NonNull", "slice_from_raw_parts") => { let (p, len) = (arg(), arg()); - let slice = match unmodeled(b, pointee(), "a slice from a pointer to") { + match unmodeled(b, pointee(), "a slice from a pointer to") { _ if b.t.zero_sized(pointee()) => { return Ok(Some(b.err( span, @@ -731,16 +773,14 @@ pub fn call<'tcx>( ))); } Some(why) => return Ok(Some(b.err(span, why))), + // A raw pointer to a slice is where it starts and how long it is, which + // nothing checks until a read through it. + None if owner != "raw" => Expr::New(Box::new(Expr::Rt("RawSlice".into())), vec![p, len]), None if matches!(pointee().kind(), ty::Uint(ty::UintTy::U8)) => { let shared = name == "from_raw_parts"; Expr::rt("ptrByteSlice", vec![p, len, Expr::lit(shared.to_string())]) } None => Expr::rt("ptrSlice", vec![p, len]), - }; - // A raw pointer to a slice is an `rt.Place` of it, as one to any value JS copies is. - match owner { - "raw" => slice, - _ => Expr::Object(vec![(Key::Name("v".into()), slice)]), } } ("mem", "forget") => Expr::undefined(), @@ -758,7 +798,7 @@ fn unmodeled<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>, what: &str) -> Option None, - Pointee::Object | Pointee::Bytes => Some(format!( + Pointee::Object | Pointee::Bytes | Pointee::Slice => Some(format!( "{what} `{ty}`, which JS keeps as an object that knows no array it is in" )), } @@ -785,15 +825,12 @@ pub fn cast<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, from: Ty<'tcx>, to: Ty<'t if let Some(why) = unmodeled(b, elem, "a pointer into an array of") { return b.err(span, why); } - // A pointer to a slice is an `rt.Place` of it, and one to an array the array, or the - // `rt.ByteSlice` of its bytes. - let s = match inner.kind() { - ty::Slice(_) => p.member("v"), - _ => p, - }; - match b.t.pointee(inner) == Pointee::Bytes || inner.is_slice() && matches!(elem.kind(), ty::Uint(ty::UintTy::U8)) { - true => Expr::rt("byteSliceAsPtr", vec![s]), - false => Expr::rt("sliceAsPtr", vec![s]), + // A pointer to a slice holds the pointer to its first element, and one to an array is + // the array, or the `rt.ByteSlice` of its bytes. + match b.t.pointee(inner) { + Pointee::Slice => p.member("ptr"), + Pointee::Bytes => Expr::rt("byteSliceAsPtr", vec![p]), + _ => Expr::rt("sliceAsPtr", vec![p]), } } @@ -818,13 +855,13 @@ fn read<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, ty: Ty<'tcx>, span: Span) -> Pointee::Place => p.member("v"), Pointee::Object => p, Pointee::Bytes => return Expr::rt("byteSliceToArray", vec![p]), + Pointee::Slice => return b.err(span, format!("a read of `{ty}`, which has no size")), }; b.t.snapshot(value, ty, span) } /// The write of `value`, a `ty`, where `p` points. fn write<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, value: Expr, ty: Ty<'tcx>, span: Span) -> Expr { - let _ = span; if b.t.zero_sized(ty) { return Expr::undefined(); } @@ -832,9 +869,15 @@ fn write<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, value: Expr, ty: Ty<'tcx>, s Pointee::Place => p.member("v").assign(value), Pointee::Object => Expr::rt("ptrWrite", vec![p, value]), Pointee::Bytes => Expr::rt("byteSliceSet", vec![p, value]), + Pointee::Slice => b.err(span, format!("a write of `{ty}`, which has no size")), } } +/// Whether `ty` is `[u8]`, whose slices are `rt.ByteSlice`s. +pub fn slice_of_bytes(ty: Ty<'_>) -> bool { + matches!(ty.kind(), ty::Slice(elem) if matches!(elem.kind(), ty::Uint(ty::UintTy::U8))) +} + /// Whether pointers `x` and `y` to `ty` point at the same place: the same object or /// bytes, or null both. pub fn compare<'tcx>(b: &mut Body<'_, '_, 'tcx>, x: Expr, y: Expr, ty: Ty<'tcx>, span: Span) -> Expr { From 010d1adc59cb8c33635990fa5ae636be89ca1eed Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:19:58 -0800 Subject: [PATCH 31/34] perf(ts): keep the slice a raw slice pointer's first read makes Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.Inline.ts | 2 +- ts/fixtures/lang/expected/elements.Storage.ts | 2 +- ts/fixtures/lang/expected/elements.ts | 40 +-- ts/fixtures/reject/census.md | 316 ++++++++++++------ ts/fixtures/reject/expected.txt | 64 ++-- ts/runtime/rt.ts | 32 +- ts/src/lower/mir/place.rs | 7 +- ts/src/lower/mir/rvalue.rs | 14 +- ts/src/lower/pointer.rs | 4 +- 9 files changed, 317 insertions(+), 164 deletions(-) diff --git a/ts/fixtures/lang/expected/elements.Inline.ts b/ts/fixtures/lang/expected/elements.Inline.ts index 269030506..b5c7daf82 100644 --- a/ts/fixtures/lang/expected/elements.Inline.ts +++ b/ts/fixtures/lang/expected/elements.Inline.ts @@ -102,7 +102,7 @@ export function drop$ops_Drop_Option_tuple2_u8_u8(self: Inline<[rt.u8, rt.u8] | export function drop$ops_Drop_sync_Arc_u8(self: Inline>): void { const len: rt.usize = self.len; - rt.dropAll(rt.rawSliceDeref(new rt.RawSlice(ptr$sync_Arc_u8(self), len))); + rt.dropAll(new rt.RawSlice(ptr$sync_Arc_u8(self), len).deref()); } //# sourceMappingURL=elements.Inline.ts.map diff --git a/ts/fixtures/lang/expected/elements.Storage.ts b/ts/fixtures/lang/expected/elements.Storage.ts index 195975e23..6b9471060 100644 --- a/ts/fixtures/lang/expected/elements.Storage.ts +++ b/ts/fixtures/lang/expected/elements.Storage.ts @@ -35,7 +35,7 @@ export function read(self: Storage, len: rt.usize): rt.ByteSlice { } export function drop$ops_Drop(self: Storage): void { - rt.rawByteSliceDeref(new rt.RawSlice(self.data, self.capacity), false); + new rt.RawSlice(self.data, self.capacity).derefBytes(false); } export { new_ as new }; diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index e3c751cb8..9fabb2846 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -76,7 +76,7 @@ export function arrays(data: rt.ByteSlice, out: rt.ByteVec): void { _40.v = rt.u16Add(_40.v, 2); rt.byteVecPush(out, rt.ptrOffsetFrom(rt.ptrAdd(p, 4), p) & 0xff); const tail: rt.RawSlice = new rt.RawSlice(rt.ptrAdd(p, i), rt.usizeSub(5, i)); - rt.byteVecPush(out, rt.rawSliceDeref(tail).length & 0xff); + rt.byteVecPush(out, tail.deref().length & 0xff); tail.ptr.v = 7; const unit: rt.Place = rt.DANGLING_UNIT; n as rt.usize; @@ -333,33 +333,33 @@ export function rawSlices(data: rt.ByteSlice, out: rt.ByteVec): void { const len: rt.usize = v.length; const p: rt.RawSlice = new rt.RawSlice(rt.sliceAsPtr(v), len); rt.idx(p, 0); - const _19: rt.u32 = rt.u32Add(rt.rawSliceDeref(p)[0], 1); - rt.rawSliceDeref(p)[0] = _19; + const _19: rt.u32 = rt.u32Add(p.deref()[0], 1); + p.deref()[0] = _19; const _20: rt.u32 = n as rt.u32; const _21: rt.usize = rt.usizeSub(len, 1); rt.idx(p, _21); - rt.rawSliceDeref(p)[_21] = _20; - rt.byteVecPush(out, rt.rawSliceDeref(p).length & 0xff); + p.deref()[_21] = _20; + rt.byteVecPush(out, p.deref().length & 0xff); rt.byteVecPush(out, p.length & 0xff); rt.idx(p, 1); - rt.byteVecPush(out, rt.rawSliceDeref(p)[1] & 0xff); - const tail: rt.u32[] = rt.sliceRangeMut(rt.rawSliceDeref(p), 1); + rt.byteVecPush(out, p.deref()[1] & 0xff); + const tail: rt.u32[] = rt.sliceRangeMut(p.deref(), 1); rt.idx(tail, 0); tail[0] = 9; - rt.byteVecPush(out, rt.iterSum(rt.iterSlice(rt.rawSliceDeref(p)), rt.u32Add, 0) & 0xff); + rt.byteVecPush(out, rt.iterSum(rt.iterSlice(p.deref()), rt.u32Add, 0) & 0xff); emit(v, out); const bytes: rt.ByteVec = rt.byteSliceToByteVec(data); rt.byteVecPush(bytes, 3); const b: rt.RawSlice = new rt.RawSlice(rt.byteVecAsPtr(bytes), bytes.length); rt.idx(b, 0); - const slice2 = rt.rawByteSliceDeref(b, false); - const slice3 = rt.rawByteSliceDeref(b, false); + const slice2 = b.derefBytes(false); + const slice3 = b.derefBytes(false); slice3.data[slice3.start] = slice2.data[slice2.start] ^ 255; - const _66: rt.usize = rt.usizeSub(rt.rawByteSliceDeref(b, false).length, 1); + const _66: rt.usize = rt.usizeSub(b.derefBytes(false).length, 1); rt.idx(b, _66); - const slice4 = rt.rawByteSliceDeref(b, false); + const slice4 = b.derefBytes(false); rt.byteVecPush(out, slice4.data[slice4.start + _66]); - rt.byteVecPush(out, rt.byteSliceRange(rt.rawByteSliceDeref(b, false), 1).length & 0xff); + rt.byteVecPush(out, rt.byteSliceRange(b.derefBytes(false), 1).length & 0xff); rt.iterExtendBytes(out, rt.iterSlice(rt.byteVecView(bytes)), undefined, true); const short: rt.u32[] = [n as rt.u32, 1]; const long: rt.RawSlice = new rt.RawSlice(rt.sliceAsPtr(short), 10); @@ -370,12 +370,12 @@ export function rawSlices(data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecPush(out, nn.length & 0xff); const _102: rt.RawSlice = nn; rt.idx(_102, 1); - rt.rawSliceDeref(_102)[1] = 11; - const _109: rt.u32[] = rt.rawSliceDeref(nn); + _102.deref()[1] = 11; + const _109: rt.u32[] = nn.deref(); rt.idx(_109, 1); rt.byteVecPush(out, _109[1] & 0xff); const pointer2 = nn; - rt.byteVecPush(out, option_mapOr$0grxkdu(pointer2.ptr === rt.NULL ? undefined : rt.rawSliceDeref(pointer2), 0, (s: rt.u32[]): rt.u32 => { + rt.byteVecPush(out, option_mapOr$0grxkdu(pointer2.ptr === rt.NULL ? undefined : pointer2.deref(), 0, (s: rt.u32[]): rt.u32 => { rt.idx(s, 0); return s[0]; }) & 0xff); @@ -383,16 +383,16 @@ export function rawSlices(data: rt.ByteSlice, out: rt.ByteVec): void { const pointer3 = null_; rt.byteVecPush(out, ((pointer3.ptr === rt.NULL ? undefined : pointer3)) === undefined ? 1 : 0); const pointer4 = null_; - rt.byteVecPush(out, ((pointer4.ptr === rt.NULL ? undefined : rt.rawSliceDeref(pointer4))) === undefined ? 1 : 0); + rt.byteVecPush(out, ((pointer4.ptr === rt.NULL ? undefined : pointer4.deref())) === undefined ? 1 : 0); const raw: rt.RawSlice = rt.rawSliceOf([n as rt.u32, 2, 3]); rt.idx(raw, 2); - rt.rawSliceDeref(raw)[2] = 8; - emit(rt.rawSliceDeref(raw), out); + raw.deref()[2] = 8; + emit(raw.deref(), out); const whole: rt.RawSlice = new rt.RawSlice(rt.sliceAsPtr(short), 2); const ofRef: rt.RawSlice = rt.rawSliceOf(rt.sliceRange(v, 1, 3)); rt.byteVecPush(out, rt.usizeAdd(whole.length, ofRef.length) & 0xff); rt.idx(ofRef, 1); - rt.byteVecPush(out, rt.rawSliceDeref(ofRef)[1] & 0xff); + rt.byteVecPush(out, ofRef.deref()[1] & 0xff); } /** diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 1c497ea8a..b15dec137 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,16 +1,16 @@ # Census of `reject-fixture` -- Refusals: 230 -- Function instances: 290 +- Refusals: 250 +- Function instances: 297 - Generic functions no instance reached, lowered over their type parameters: 13 ## By construct | Count | Construct | First | | ---: | --- | --- | -| 49 | a raw pointer to `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:593:10 | -| 25 | unsupported call to `_` | fixtures/reject/src/lib.rs:160:8 | -| 22 | a raw pointer to `_` in a variable assigned again while it may point there | fixtures/reject/src/lib.rs:601:10 | +| 50 | a raw pointer to `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:593:10 | +| 23 | a raw pointer to `_` in a variable assigned again while it may point there | fixtures/reject/src/lib.rs:601:10 | +| 20 | unsupported call to `_` | fixtures/reject/src/lib.rs:160:8 | | 15 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:1322:33 | | 15 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | fixtures/reject/src/lib.rs:1640:18 | | 5 | `_` with a mutable reference to a value JS copies, which its mapping would copy | fixtures/reject/src/lib.rs:1220:22 | @@ -18,16 +18,22 @@ | 4 | a variant of `_`, which is not supported: either is the same JS string | fixtures/reject/src/lib.rs:316:2 | | 4 | an assignment through a reference to `_` | fixtures/reject/src/lib.rs:83:2 | | 3 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | fixtures/reject/src/lib.rs:21:4 | +| 3 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:1695:3 | | 3 | a copy of a closure that changes what it captured | fixtures/reject/src/lib.rs:224:14 | -| 3 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | fixtures/reject/src/lib.rs:2149:2 | +| 3 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | fixtures/reject/src/lib.rs:2251:2 | +| 3 | an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:1698:56 | | 3 | replacing the value behind a reference to `_` | fixtures/reject/src/lib.rs:91:2 | | 3 | unsupported type `_` | fixtures/reject/src/lib.rs:7:14 | | 2 | `_` is not supported: its `None` and `Some` would both be `rt.None` | fixtures/reject/src/lib.rs:16:15 | | 2 | `_` of `_`, whose `None` is `rt.None`, which is not supported yet | fixtures/reject/src/lib.rs:37:4 | | 2 | `_`, a trait object of another crate's trait, which is not supported yet | fixtures/reject/src/lib.rs:11:16 | | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | fixtures/reject/src/lib.rs:449:23 | +| 2 | `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/lib.rs:2210:5 | +| 2 | `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/lib.rs:2212:24 | | 2 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:540:27 | | 2 | a coercion to `_`, which is not supported yet | fixtures/reject/src/lib.rs:440:40 | +| 2 | a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2235:18 | +| 2 | a raw or mutable slice of a byte in no array of bytes | fixtures/reject/src/lib.rs:2221:12 | | 2 | a raw pointer to a place of `()`, which has no address of its own | fixtures/reject/src/lib.rs:713:15 | | 2 | an iterator of the crate implementing `_`, which is not supported yet | fixtures/reject/src/lib.rs:145:1 | | 2 | comparing pointers to `_`, each a place JS makes of its own | fixtures/reject/src/lib.rs:581:2 | @@ -49,24 +55,33 @@ | 1 | `clone_from` of `_`, whose items' `clone_from` is their own, which is not supported yet | fixtures/reject/src/lib.rs:2103:4 | | 1 | `collect` of `_`, which sorts by `_`'s own `Ord`, of entries that drop, which is not supported yet | fixtures/reject/src/lib.rs:940:22 | | 1 | `fmt::Arguments` with a placeholder, or not made in place, written to `_`'s own `fmt::Write`, which is not supported yet: std splits what a placeholder writes into `write_str` calls the runtime does not make | fixtures/reject/src/lib.rs:55:10 | +| 1 | `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/lib.rs:2215:21 | +| 1 | `offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays | fixtures/reject/src/lib.rs:2214:20 | +| 1 | `offset_from` of pointers to `()`, which has no bytes | fixtures/reject/src/lib.rs:2157:13 | +| 1 | `offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2150:36 | | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | fixtures/reject/src/lib.rs:209:40 | | 1 | a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet | fixtures/reject/src/lib.rs:202:8 | | 1 | a cast from `_` to `_` | fixtures/reject/src/lib.rs:68:2 | -| 1 | a cast from `_` to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:1698:33 | -| 1 | a coercion of `_` to `_`, a pointer to a slice | fixtures/reject/src/lib.rs:1699:28 | | 1 | a coercion to `_` | fixtures/reject/src/lib.rs:276:2 | | 1 | a constant of type `_`, a pointer into memory translation can't name | fixtures/reject/src/lib.rs:763:3 | +| 1 | a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2233:3 | | 1 | a dangling pointer to `_`, a value of no bytes made of more than structs and tuples | fixtures/reject/src/lib.rs:722:11 | | 1 | a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is | fixtures/reject/src/lib.rs:77:34 | | 1 | a mapped guard of `_`, which JS can't write in place | fixtures/reject/src/lib.rs:2137:10 | | 1 | a mutable reference into a `Result`'s payload, passed on or kept | fixtures/reject/src/lib.rs:347:12 | | 1 | a mutable static, which any code may write | fixtures/reject/src/lib.rs:61:11 | +| 1 | a pointer into `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2144:12 | +| 1 | a pointer into an array of `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2149:30 | +| 1 | a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2237:27 | | 1 | a pointer's address, which JS has no counterpart of | fixtures/reject/src/lib.rs:545:17 | | 1 | a printable type must implement `Display` or `Debug` | fixtures/reject/src/lib.rs:64:1 | | 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | fixtures/reject/src/lib.rs:562:10 | | 1 | a reference through which a raw pointer may be made into `_` in an element, a payload, or a union field, which is not supported yet | fixtures/reject/src/lib.rs:1349:11 | | 1 | a scalar constant of type `_` | fixtures/reject/src/lib.rs:8:2 | -| 1 | a slice of one byte's place, which a byte slice can view only in an array of bytes | fixtures/reject/src/lib.rs:2143:2 | +| 1 | a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2148:11 | +| 1 | a slice of `()`, which has no bytes, from a pointer | fixtures/reject/src/lib.rs:2157:39 | +| 1 | a slice of one byte's place, which a byte slice can view only in an array of bytes | fixtures/reject/src/lib.rs:2245:2 | +| 1 | a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust | fixtures/reject/src/lib.rs:2236:6 | | 1 | a trait object of `_`, which is not supported yet | fixtures/reject/src/lib.rs:466:26 | | 1 | an upcast from `_` to `_`, which is not supported yet | fixtures/reject/src/lib.rs:517:32 | | 1 | arithmetic on `_` | fixtures/reject/src/lib.rs:8:2 | @@ -90,23 +105,21 @@ The type or module each refused type or path belongs to. | ---: | --- | --- | | 39 | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:672:15 | | 27 | `(u32, u32)` | fixtures/reject/src/lib.rs:593:10 | -| 6 | `[u8; 4]` | fixtures/reject/src/lib.rs:466:26 | +| 11 | `(u8, u8)` | fixtures/reject/src/lib.rs:1257:31 | +| 9 | `[u8; 4]` | fixtures/reject/src/lib.rs:466:26 | +| 7 | `u32` | fixtures/reject/src/lib.rs:540:27 | | 6 | `f16` | fixtures/reject/src/lib.rs:7:14 | | 6 | `std::borrow::Cow` | fixtures/reject/src/lib.rs:316:2 | -| 6 | `u32` | fixtures/reject/src/lib.rs:540:27 | -| 5 | `(u8, u8)` | fixtures/reject/src/lib.rs:1257:31 | | 5 | `Gap` | fixtures/reject/src/lib.rs:893:10 | | 5 | `Option` | fixtures/reject/src/lib.rs:16:15 | -| 5 | `u8` | fixtures/reject/src/lib.rs:253:6 | | 4 | `Iterator` | fixtures/reject/src/lib.rs:37:4 | | 4 | `std::pin::Pin` | fixtures/reject/src/lib.rs:91:2 | | 4 | `std::str` | fixtures/reject/src/lib.rs:424:4 | +| 4 | `u8` | fixtures/reject/src/lib.rs:253:6 | | 4 | `{closure}` | fixtures/reject/src/lib.rs:304:13 | -| 3 | `*const [u8; 4]` | fixtures/reject/src/lib.rs:1698:33 | | 3 | `Mid` | fixtures/reject/src/lib.rs:1340:34 | | 3 | `Outer` | fixtures/reject/src/lib.rs:1340:34 | | 3 | `Reused` | fixtures/reject/src/lib.rs:21:4 | -| 3 | `std::ptr` | fixtures/reject/src/lib.rs:1695:3 | | 3 | `std::vec::Vec` | fixtures/reject/src/lib.rs:295:16 | | 2 | `(u16, u16)` | fixtures/reject/src/lib.rs:1161:31 | | 2 | `Counted` | fixtures/reject/src/lib.rs:374:2 | @@ -117,6 +130,7 @@ The type or module each refused type or path belongs to. | 2 | `Inside` | fixtures/reject/src/lib.rs:1830:20 | | 2 | `Poll` | fixtures/reject/src/lib.rs:72:20 | | 2 | `Scaled` | fixtures/reject/src/lib.rs:449:23 | +| 2 | `[u32; 3]` | fixtures/reject/src/lib.rs:2175:10 | | 2 | `[u8; 2]` | fixtures/reject/src/lib.rs:1711:37 | | 2 | `[u8; 3]` | fixtures/reject/src/lib.rs:1711:15 | | 2 | `dyn Scaled` | fixtures/reject/src/lib.rs:449:23 | @@ -129,15 +143,14 @@ The type or module each refused type or path belongs to. | 2 | `std::mem::MaybeUninit` | fixtures/reject/src/lib.rs:907:19 | | 2 | `std::ops::Range` | fixtures/reject/src/lib.rs:160:8 | | 2 | `std::option::Option` | fixtures/reject/src/lib.rs:354:2 | -| 2 | `std::ptr::const_ptr` | fixtures/reject/src/lib.rs:1698:73 | | 2 | `std::std` | fixtures/reject/src/lib.rs:552:11 | | 2 | `std::sync::Arc` | fixtures/reject/src/lib.rs:276:2 | | 2 | `std::sync::MutexGuard` | fixtures/reject/src/lib.rs:83:2 | | 2 | `std::vec::IntoIter` | fixtures/reject/src/lib.rs:419:17 | -| 1 | `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2248:2 | -| 1 | `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2232:2 | -| 1 | `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2266:13 | -| 1 | `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2297:2 | +| 1 | `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2350:2 | +| 1 | `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2334:2 | +| 1 | `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2368:13 | +| 1 | `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2399:2 | | 1 | `(std::vec::Vec, Tens)` | fixtures/reject/src/lib.rs:1257:31 | | 1 | `(u16, ByKeyReplace)` | fixtures/reject/src/lib.rs:1606:55 | | 1 | `(u16, ByKeySet)` | fixtures/reject/src/lib.rs:1598:51 | @@ -145,8 +158,7 @@ The type or module each refused type or path belongs to. | 1 | `(u16, ByRefReplace)` | fixtures/reject/src/lib.rs:1582:32 | | 1 | `(u16, ByRefTake)` | fixtures/reject/src/lib.rs:1590:32 | | 1 | `(u16, InTuple)` | fixtures/reject/src/lib.rs:1331:33 | -| 1 | `*const [u8]` | fixtures/reject/src/lib.rs:1699:28 | -| 1 | `*const u8` | fixtures/reject/src/lib.rs:1698:33 | +| 1 | `*const [u8; 4]` | fixtures/reject/src/lib.rs:1701:5 | | 1 | `*mut [u8; 4]` | fixtures/reject/src/lib.rs:1697:41 | | 1 | `Aliased` | fixtures/reject/src/lib.rs:1105:29 | | 1 | `Assigned` | fixtures/reject/src/lib.rs:1997:20 | @@ -173,7 +185,7 @@ The type or module each refused type or path belongs to. | 1 | `BytesMut` | fixtures/reject/src/lib.rs:368:6 | | 1 | `Called` | fixtures/reject/src/lib.rs:1071:28 | | 1 | `Chosen` | fixtures/reject/src/lib.rs:1891:17 | -| 1 | `Clone` | fixtures/reject/src/lib.rs:2154:4 | +| 1 | `Clone` | fixtures/reject/src/lib.rs:2256:4 | | 1 | `Closed` | fixtures/reject/src/lib.rs:2063:15 | | 1 | `Compared` | fixtures/reject/src/lib.rs:1858:22 | | 1 | `Copied` | fixtures/reject/src/lib.rs:1161:31 | @@ -192,6 +204,7 @@ The type or module each refused type or path belongs to. | 1 | `InGeneric` | fixtures/reject/src/lib.rs:1307:9 | | 1 | `InNested` | fixtures/reject/src/lib.rs:1295:9 | | 1 | `Lane` | fixtures/reject/src/lib.rs:857:19 | +| 1 | `Lanes` | fixtures/reject/src/lib.rs:2177:10 | | 1 | `Loaded` | fixtures/reject/src/lib.rs:1127:29 | | 1 | `Looped` | fixtures/reject/src/lib.rs:1095:26 | | 1 | `Mapped` | fixtures/reject/src/lib.rs:1172:39 | @@ -224,31 +237,32 @@ The type or module each refused type or path belongs to. | 1 | `[InArray; 2]` | fixtures/reject/src/lib.rs:1322:33 | | 1 | `[InWritten; 1]` | fixtures/reject/src/lib.rs:1383:35 | | 1 | `[std::cell::UnsafeCell; 2]` | fixtures/reject/src/lib.rs:2076:20 | +| 1 | `[u32; 2]` | fixtures/reject/src/lib.rs:2162:13 | | 1 | `dyn Keyed` | fixtures/reject/src/lib.rs:517:32 | | 1 | `poll` | fixtures/reject/src/lib.rs:487:2 | | 1 | `rt.Poll` | fixtures/reject/src/lib.rs:487:2 | | 1 | `std::any::Any` | fixtures/reject/src/lib.rs:523:4 | | 1 | `std::cell::Cell` | fixtures/reject/src/lib.rs:940:22 | -| 1 | `std::cell::LazyCell` | fixtures/reject/src/lib.rs:2191:3 | +| 1 | `std::cell::LazyCell` | fixtures/reject/src/lib.rs:2293:3 | | 1 | `std::collections::BTreeMap` | fixtures/reject/src/lib.rs:940:22 | | 1 | `std::collections::HashMap` | fixtures/reject/src/lib.rs:253:6 | | 1 | `std::default::Default` | fixtures/reject/src/lib.rs:2104:2 | | 1 | `std::iter::Map` | fixtures/reject/src/lib.rs:209:40 | | 1 | `std::iter::Zip` | fixtures/reject/src/lib.rs:202:8 | | 1 | `std::iter` | fixtures/reject/src/lib.rs:1220:22 | -| 1 | `std::mem` | fixtures/reject/src/lib.rs:2159:2 | +| 1 | `std::mem` | fixtures/reject/src/lib.rs:2261:2 | | 1 | `std::net::AddrParseError` | fixtures/reject/src/lib.rs:423:32 | | 1 | `std::net::Ipv4Addr` | fixtures/reject/src/lib.rs:423:32 | | 1 | `std::num` | fixtures/reject/src/lib.rs:1262:4 | | 1 | `std::ops::RangeFrom` | fixtures/reject/src/lib.rs:969:32 | | 1 | `std::ops::RangeTo` | fixtures/reject/src/lib.rs:969:11 | -| 1 | `std::ptr::mut_ptr` | fixtures/reject/src/lib.rs:1698:56 | +| 1 | `std::ptr` | fixtures/reject/src/lib.rs:1697:14 | | 1 | `std::slice::IterMut` | fixtures/reject/src/lib.rs:751:15 | | 1 | `std::std::Layout` | fixtures/reject/src/lib.rs:550:15 | -| 1 | `std::sync::LazyLock` | fixtures/reject/src/lib.rs:2196:2 | +| 1 | `std::sync::LazyLock` | fixtures/reject/src/lib.rs:2298:2 | | 1 | `std::sync::Weak` | fixtures/reject/src/lib.rs:440:40 | | 1 | `std::task::Poll` | fixtures/reject/src/lib.rs:77:34 | -| 1 | `std::thread::LocalKey` | fixtures/reject/src/lib.rs:2202:2 | +| 1 | `std::thread::LocalKey` | fixtures/reject/src/lib.rs:2304:2 | | 1 | `str` | fixtures/reject/src/lib.rs:311:4 | | 1 | `u64` | fixtures/reject/src/lib.rs:68:2 | | 1 | `value` | fixtures/reject/src/lib.rs:31:2 | @@ -266,6 +280,7 @@ The type or module each refused type or path belongs to. | 2 | `_` of `_`, whose `None` is `rt.None`, which is not supported yet | `Iterator::scan`, `Option` | fixtures/reject/src/lib.rs:37:4 | | 2 | `_`, a trait object of another crate's trait, which is not supported yet | `dyn std::iter::Iterator` | fixtures/reject/src/lib.rs:11:16 | | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | `dyn Scaled`, `Scaled` | fixtures/reject/src/lib.rs:449:23 | +| 2 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | `[u8; 4]` | fixtures/reject/src/lib.rs:1695:3 | | 2 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::mem::MaybeUninit`, `Gap` | fixtures/reject/src/lib.rs:907:19 | | 2 | a raw pointer to `_` in a variable assigned again while it may point there | `[u8; 4]` | fixtures/reject/src/lib.rs:1718:26 | | 2 | a raw pointer to `_` in a variable assigned again while it may point there | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:681:15 | @@ -274,6 +289,7 @@ The type or module each refused type or path belongs to. | 2 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `Inside` | fixtures/reject/src/lib.rs:1830:20 | | 2 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `[u8; 4]` | fixtures/reject/src/lib.rs:1718:26 | | 2 | an assignment through a reference to `_` | `std::pin::Pin` | fixtures/reject/src/lib.rs:107:2 | +| 2 | an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in | `[u8; 4]` | fixtures/reject/src/lib.rs:1698:56 | | 2 | comparing pointers to `_`, each a place JS makes of its own | `u32` | fixtures/reject/src/lib.rs:581:2 | | 2 | replacing the value behind a reference to `_` | `std::pin::Pin` | fixtures/reject/src/lib.rs:91:2 | | 2 | unsupported call to `_` | `::sum` | fixtures/reject/src/lib.rs:419:17 | @@ -287,7 +303,7 @@ The type or module each refused type or path belongs to. | 1 | `_` is not supported: its field `_` is `_`, typed `_`, whose `Pending` would be `rt.OuterPending` as `_` | `Waiting`, `poll`, `Poll`, `rt.Poll`, `Poll` | fixtures/reject/src/lib.rs:487:2 | | 1 | `_` of `_` by `_`, which is not supported yet | `HashMap::contains_key`, `std::collections::HashMap`, `u8` | fixtures/reject/src/lib.rs:253:6 | | 1 | `_` of `_`, which is not supported yet | `std::any::Any::type_id`, `dyn std::any::Any` | fixtures/reject/src/lib.rs:523:4 | -| 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `Clone::clone_from`, `std::vec::Vec` | fixtures/reject/src/lib.rs:2154:4 | +| 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `Clone::clone_from`, `std::vec::Vec` | fixtures/reject/src/lib.rs:2256:4 | | 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `slice::clone_from_slice`, `Reused` | fixtures/reject/src/lib.rs:271:4 | | 1 | `_` of `_`, whose `clone_from` is its own, which is not supported yet | `slice::fill`, `Reused` | fixtures/reject/src/lib.rs:21:4 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::collections::LinkedList::retain` | fixtures/reject/src/lib.rs:2102:4 | @@ -299,20 +315,22 @@ The type or module each refused type or path belongs to. | 1 | `clone_from` of `_`, whose items' `clone_from` is their own, which is not supported yet | `std::collections::LinkedList` | fixtures/reject/src/lib.rs:2103:4 | | 1 | `collect` of `_`, which sorts by `_`'s own `Ord`, of entries that drop, which is not supported yet | `std::collections::BTreeMap`, `std::cell::Cell` | fixtures/reject/src/lib.rs:940:22 | | 1 | `fmt::Arguments` with a placeholder, or not made in place, written to `_`'s own `fmt::Write`, which is not supported yet: std splits what a placeholder writes into `write_str` calls the runtime does not make | `Sink` | fixtures/reject/src/lib.rs:55:10 | +| 1 | `offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2150:36 | | 1 | a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet | `std::iter::Map` | fixtures/reject/src/lib.rs:209:40 | | 1 | a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet | `std::iter::Zip` | fixtures/reject/src/lib.rs:202:8 | | 1 | a cast from `_` to `_` | `u64`, `f16` | fixtures/reject/src/lib.rs:68:2 | -| 1 | a cast from `_` to `_`, which reads the bytes of one type as another | `*const [u8; 4]`, `*const u8` | fixtures/reject/src/lib.rs:1698:33 | -| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `[u8; 4]`, `u8` | fixtures/reject/src/lib.rs:1698:17 | +| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `[u32; 2]` | fixtures/reject/src/lib.rs:2162:13 | | 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `u8` | fixtures/reject/src/lib.rs:540:27 | -| 1 | a coercion of `_` to `_`, a pointer to a slice | `*const [u8; 4]`, `*const [u8]` | fixtures/reject/src/lib.rs:1699:28 | | 1 | a coercion to `_` | `std::sync::Arc` | fixtures/reject/src/lib.rs:276:2 | | 1 | a coercion to `_`, which is not supported yet | `&Tail` | fixtures/reject/src/lib.rs:476:30 | | 1 | a coercion to `_`, which is not supported yet | `std::sync::Weak` | fixtures/reject/src/lib.rs:440:40 | | 1 | a constant of type `_`, a pointer into memory translation can't name | `&u32` | fixtures/reject/src/lib.rs:763:3 | +| 1 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2147:3 | | 1 | a dangling pointer to `_`, a value of no bytes made of more than structs and tuples | `Only` | fixtures/reject/src/lib.rs:722:11 | | 1 | a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is | `std::task::Poll` | fixtures/reject/src/lib.rs:77:34 | | 1 | a mapped guard of `_`, which JS can't write in place | `{closure}` | fixtures/reject/src/lib.rs:2137:10 | +| 1 | a pointer into `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2144:12 | +| 1 | a pointer into an array of `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2149:30 | | 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | `std::vec::Vec` | fixtures/reject/src/lib.rs:562:10 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u16, u16)`, `Copied` | fixtures/reject/src/lib.rs:1161:31 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u16, u16)`, `Pushed` | fixtures/reject/src/lib.rs:1233:22 | @@ -322,15 +340,16 @@ The type or module each refused type or path belongs to. | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u32, u32)`, `Track` | fixtures/reject/src/lib.rs:837:19 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u32, u32)`, `Window` | fixtures/reject/src/lib.rs:654:2 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u32, u32)`, `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:672:15 | -| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2248:2 | -| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2232:2 | -| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2266:13 | -| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2297:2 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2350:2 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2334:2 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2368:13 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u8, u8)`, `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2399:2 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByKeyReplace`, `(u16, ByKeyReplace)` | fixtures/reject/src/lib.rs:1606:55 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByKeySet`, `(u16, ByKeySet)` | fixtures/reject/src/lib.rs:1598:51 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByKeyTake`, `(u16, ByKeyTake)` | fixtures/reject/src/lib.rs:1613:52 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByRefReplace`, `(u16, ByRefReplace)` | fixtures/reject/src/lib.rs:1582:32 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `ByRefTake`, `(u16, ByRefTake)` | fixtures/reject/src/lib.rs:1590:32 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `[u32; 3]`, `Lanes` | fixtures/reject/src/lib.rs:2177:10 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `Aliased` | fixtures/reject/src/lib.rs:1105:29 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `ByBox` | fixtures/reject/src/lib.rs:1489:35 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `ByClosure` | fixtures/reject/src/lib.rs:1504:35 | @@ -359,6 +378,7 @@ The type or module each refused type or path belongs to. | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `Repeated` | fixtures/reject/src/lib.rs:1206:18 | | 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::cell::UnsafeCell`, `Rewritten` | fixtures/reject/src/lib.rs:1115:47 | | 1 | a raw pointer to `_` in a variable assigned again while it may point there | `Target` | fixtures/reject/src/lib.rs:1654:10 | +| 1 | a raw pointer to `_` in a variable assigned again while it may point there | `[u32; 3]` | fixtures/reject/src/lib.rs:2175:10 | | 1 | a raw pointer to `_` in an element, a payload, or a union field, which is not supported yet | `(u32, u32)` | fixtures/reject/src/lib.rs:664:10 | | 1 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | `InArray`, `[InArray; 2]` | fixtures/reject/src/lib.rs:1322:33 | | 1 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | `InTuple`, `(u16, InTuple)` | fixtures/reject/src/lib.rs:1331:33 | @@ -385,14 +405,16 @@ The type or module each refused type or path belongs to. | 1 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:1640:18 | | 1 | a reference to `_`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there | `std::option::Option` | fixtures/reject/src/lib.rs:2093:13 | | 1 | a scalar constant of type `_` | `f16` | fixtures/reject/src/lib.rs:8:2 | -| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `Reused` | fixtures/reject/src/lib.rs:2149:2 | -| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `std::cell::LazyCell` | fixtures/reject/src/lib.rs:2191:3 | -| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `std::thread::LocalKey` | fixtures/reject/src/lib.rs:2202:2 | +| 1 | a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2148:11 | +| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `Reused` | fixtures/reject/src/lib.rs:2251:2 | +| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `std::cell::LazyCell` | fixtures/reject/src/lib.rs:2293:3 | +| 1 | a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there | `std::thread::LocalKey` | fixtures/reject/src/lib.rs:2304:2 | | 1 | a trait object of `_`, which is not supported yet | `[u8; 4]` | fixtures/reject/src/lib.rs:466:26 | -| 1 | an assignment through a reference to `_` | `std::sync::LazyLock` | fixtures/reject/src/lib.rs:2196:2 | +| 1 | an assignment through a reference to `_` | `std::sync::LazyLock` | fixtures/reject/src/lib.rs:2298:2 | | 1 | an assignment through a reference to `_` | `std::sync::MutexGuard` | fixtures/reject/src/lib.rs:83:2 | | 1 | an iterator of the crate implementing `_`, which is not supported yet | `DoubleEndedIterator` | fixtures/reject/src/lib.rs:145:1 | | 1 | an iterator of the crate implementing `_`, which is not supported yet | `FusedIterator` | fixtures/reject/src/lib.rs:193:1 | +| 1 | an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in | `(u8, u8)` | fixtures/reject/src/lib.rs:2146:13 | | 1 | an upcast from `_` to `_`, which is not supported yet | `dyn Keyed`, `dyn std::any::Any` | fixtures/reject/src/lib.rs:517:32 | | 1 | arithmetic on `_` | `f16` | fixtures/reject/src/lib.rs:8:2 | | 1 | comparing `_` | `*mut [u8; 4]` | fixtures/reject/src/lib.rs:1697:41 | @@ -418,17 +440,12 @@ The type or module each refused type or path belongs to. | 1 | unsupported call to `_` | `::hash` | fixtures/reject/src/lib.rs:286:10 | | 1 | unsupported call to `_` | `std::any::TypeId::of` | fixtures/reject/src/lib.rs:523:17 | | 1 | unsupported call to `_` | `std::borrow::Cow::is_borrowed` | fixtures/reject/src/lib.rs:328:2 | -| 1 | unsupported call to `_` | `std::mem::size_of_val` | fixtures/reject/src/lib.rs:2159:2 | +| 1 | unsupported call to `_` | `std::mem::size_of_val` | fixtures/reject/src/lib.rs:2261:2 | | 1 | unsupported call to `_` | `std::num::is_ascii_octdigit` | fixtures/reject/src/lib.rs:1262:4 | | 1 | unsupported call to `_` | `std::ops::Range::contains` | fixtures/reject/src/lib.rs:160:8 | | 1 | unsupported call to `_` | `std::ops::Range::is_empty` | fixtures/reject/src/lib.rs:974:9 | | 1 | unsupported call to `_` | `std::ops::RangeTo::contains` | fixtures/reject/src/lib.rs:969:11 | | 1 | unsupported call to `_` | `std::ptr::addr_eq` | fixtures/reject/src/lib.rs:1697:14 | -| 1 | unsupported call to `_` | `std::ptr::const_ptr::len` | fixtures/reject/src/lib.rs:1702:58 | -| 1 | unsupported call to `_` | `std::ptr::const_ptr::offset` | fixtures/reject/src/lib.rs:1698:73 | -| 1 | unsupported call to `_` | `std::ptr::copy_nonoverlapping` | fixtures/reject/src/lib.rs:1696:3 | -| 1 | unsupported call to `_` | `std::ptr::copy` | fixtures/reject/src/lib.rs:1695:3 | -| 1 | unsupported call to `_` | `std::ptr::mut_ptr::add` | fixtures/reject/src/lib.rs:1698:56 | | 1 | unsupported call to `_` | `std::std::Layout::new` | fixtures/reject/src/lib.rs:550:15 | | 1 | unsupported call to `_` | `std::std::alloc` | fixtures/reject/src/lib.rs:552:11 | | 1 | unsupported call to `_` | `std::std::dealloc` | fixtures/reject/src/lib.rs:555:3 | @@ -439,7 +456,7 @@ The type or module each refused type or path belongs to. ## Spans -
a raw pointer to `_` in `_`, which the crate may put another object in place of (49) +
a raw pointer to `_` in `_`, which the crate may put another object in place of (50) - fixtures/reject/src/lib.rs:593:10: a raw pointer to `(u32, u32)` in `Span2`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:654:2: a raw pointer to `(u32, u32)` in `Window`, which the crate may put another object in place of @@ -486,44 +503,15 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:1598:51: a raw pointer to `ByKeySet` in `(u16, ByKeySet)`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:1606:55: a raw pointer to `ByKeyReplace` in `(u16, ByKeyReplace)`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:1613:52: a raw pointer to `ByKeyTake` in `(u16, ByKeyTake)`, which the crate may put another object in place of -- fixtures/reject/src/lib.rs:2232:2: a raw pointer to `(u8, u8)` in `([Cloned; 2], (u8, u8))`, which the crate may put another object in place of -- fixtures/reject/src/lib.rs:2248:2: a raw pointer to `(u8, u8)` in `(Dropped, (u8, u8))`, which the crate may put another object in place of -- fixtures/reject/src/lib.rs:2266:13: a raw pointer to `(u8, u8)` in `([Greatest; 2], (u8, u8))`, which the crate may put another object in place of -- fixtures/reject/src/lib.rs:2297:2: a raw pointer to `(u8, u8)` in `([Touched; 2], (u8, u8))`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2177:10: a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2334:2: a raw pointer to `(u8, u8)` in `([Cloned; 2], (u8, u8))`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2350:2: a raw pointer to `(u8, u8)` in `(Dropped, (u8, u8))`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2368:13: a raw pointer to `(u8, u8)` in `([Greatest; 2], (u8, u8))`, which the crate may put another object in place of +- fixtures/reject/src/lib.rs:2399:2: a raw pointer to `(u8, u8)` in `([Touched; 2], (u8, u8))`, which the crate may put another object in place of
-
unsupported call to `_` (25) - -- fixtures/reject/src/lib.rs:160:8: unsupported call to `std::ops::Range::::contains::` -- fixtures/reject/src/lib.rs:286:10: unsupported call to `::hash::` -- fixtures/reject/src/lib.rs:328:2: unsupported call to `std::borrow::Cow::<'_, [u8]>::is_borrowed` -- fixtures/reject/src/lib.rs:354:2: unsupported call to ` as std::cmp::PartialEq>::eq` -- fixtures/reject/src/lib.rs:419:17: unsupported call to ` as std::iter::Iterator>::sum::` -- fixtures/reject/src/lib.rs:419:38: unsupported call to ` as std::iter::Iterator>::sum::` -- fixtures/reject/src/lib.rs:424:4: unsupported call to `core::str::::parse::` -- fixtures/reject/src/lib.rs:523:2: unsupported call to `::eq` -- fixtures/reject/src/lib.rs:523:17: unsupported call to `std::any::TypeId::of::` -- fixtures/reject/src/lib.rs:550:15: unsupported call to `std::alloc::Layout::new::` -- fixtures/reject/src/lib.rs:552:11: unsupported call to `std::alloc::alloc` -- fixtures/reject/src/lib.rs:555:3: unsupported call to `std::alloc::dealloc` -- fixtures/reject/src/lib.rs:751:15: unsupported call to ` as std::iter::Iterator>::is_sorted` -- fixtures/reject/src/lib.rs:969:11: unsupported call to `std::ops::RangeTo::::contains::` -- fixtures/reject/src/lib.rs:969:32: unsupported call to ` as std::ops::RangeBounds>::contains::` -- fixtures/reject/src/lib.rs:974:9: unsupported call to `std::ops::Range::<&mut u8>::is_empty` -- fixtures/reject/src/lib.rs:1262:4: unsupported call to `core::num::::is_ascii_octdigit` -- fixtures/reject/src/lib.rs:1621:13: unsupported call to `core::str::::as_bytes_mut` -- fixtures/reject/src/lib.rs:1695:3: unsupported call to `std::ptr::copy::<[u8; 4]>` -- fixtures/reject/src/lib.rs:1696:3: unsupported call to `std::ptr::copy_nonoverlapping::<[u8; 4]>` -- fixtures/reject/src/lib.rs:1697:14: unsupported call to `std::ptr::addr_eq::<[u8; 4], [u8; 4]>` -- fixtures/reject/src/lib.rs:1698:56: unsupported call to `std::ptr::mut_ptr::::add` -- fixtures/reject/src/lib.rs:1698:73: unsupported call to `std::ptr::const_ptr::::offset` -- fixtures/reject/src/lib.rs:1702:58: unsupported call to `std::ptr::const_ptr::::len` -- fixtures/reject/src/lib.rs:2159:2: unsupported call to `std::mem::size_of_val::` - -
- -
a raw pointer to `_` in a variable assigned again while it may point there (22) +
a raw pointer to `_` in a variable assigned again while it may point there (23) - fixtures/reject/src/lib.rs:601:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there - fixtures/reject/src/lib.rs:609:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there @@ -544,9 +532,35 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:1682:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there - fixtures/reject/src/lib.rs:1718:26: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there - fixtures/reject/src/lib.rs:1719:10: a raw pointer to `[u8; 4]` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:2165:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:2173:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there -- fixtures/reject/src/lib.rs:2181:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2175:10: a raw pointer to `[u32; 3]` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2267:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2275:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there +- fixtures/reject/src/lib.rs:2283:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there + +
+ +
unsupported call to `_` (20) + +- fixtures/reject/src/lib.rs:160:8: unsupported call to `std::ops::Range::::contains::` +- fixtures/reject/src/lib.rs:286:10: unsupported call to `::hash::` +- fixtures/reject/src/lib.rs:328:2: unsupported call to `std::borrow::Cow::<'_, [u8]>::is_borrowed` +- fixtures/reject/src/lib.rs:354:2: unsupported call to ` as std::cmp::PartialEq>::eq` +- fixtures/reject/src/lib.rs:419:17: unsupported call to ` as std::iter::Iterator>::sum::` +- fixtures/reject/src/lib.rs:419:38: unsupported call to ` as std::iter::Iterator>::sum::` +- fixtures/reject/src/lib.rs:424:4: unsupported call to `core::str::::parse::` +- fixtures/reject/src/lib.rs:523:2: unsupported call to `::eq` +- fixtures/reject/src/lib.rs:523:17: unsupported call to `std::any::TypeId::of::` +- fixtures/reject/src/lib.rs:550:15: unsupported call to `std::alloc::Layout::new::` +- fixtures/reject/src/lib.rs:552:11: unsupported call to `std::alloc::alloc` +- fixtures/reject/src/lib.rs:555:3: unsupported call to `std::alloc::dealloc` +- fixtures/reject/src/lib.rs:751:15: unsupported call to ` as std::iter::Iterator>::is_sorted` +- fixtures/reject/src/lib.rs:969:11: unsupported call to `std::ops::RangeTo::::contains::` +- fixtures/reject/src/lib.rs:969:32: unsupported call to ` as std::ops::RangeBounds>::contains::` +- fixtures/reject/src/lib.rs:974:9: unsupported call to `std::ops::Range::<&mut u8>::is_empty` +- fixtures/reject/src/lib.rs:1262:4: unsupported call to `core::num::::is_ascii_octdigit` +- fixtures/reject/src/lib.rs:1621:13: unsupported call to `core::str::::as_bytes_mut` +- fixtures/reject/src/lib.rs:1697:14: unsupported call to `std::ptr::addr_eq::<[u8; 4], [u8; 4]>` +- fixtures/reject/src/lib.rs:2261:2: unsupported call to `std::mem::size_of_val::`
@@ -624,7 +638,7 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:83:2: an assignment through a reference to `std::sync::MutexGuard<'_, u8>` - fixtures/reject/src/lib.rs:107:2: an assignment through a reference to `std::pin::Pin>>` - fixtures/reject/src/lib.rs:214:2: an assignment through a reference to `std::pin::Pin<&mut Noisy>` -- fixtures/reject/src/lib.rs:2196:2: an assignment through a reference to `std::sync::LazyLock` +- fixtures/reject/src/lib.rs:2298:2: an assignment through a reference to `std::sync::LazyLock`
@@ -632,7 +646,15 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:21:4: `slice::fill` of `Reused`, whose `clone_from` is its own, which is not supported yet - fixtures/reject/src/lib.rs:271:4: `slice::clone_from_slice` of `Reused`, whose `clone_from` is its own, which is not supported yet -- fixtures/reject/src/lib.rs:2154:4: `Clone::clone_from` of `std::vec::Vec`, whose `clone_from` is its own, which is not supported yet +- fixtures/reject/src/lib.rs:2256:4: `Clone::clone_from` of `std::vec::Vec`, whose `clone_from` is its own, which is not supported yet + +
+ +
a copy between pointers to `_`, which JS keeps as an object that knows no array it is in (3) + +- fixtures/reject/src/lib.rs:1695:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:1696:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:2147:3: a copy between pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in
@@ -646,9 +668,17 @@ The type or module each refused type or path belongs to.
a slice or a cell of a mutable reference to `_` that names no place, which a whole write would replace only there (3) -- fixtures/reject/src/lib.rs:2149:2: a slice or a cell of a mutable reference to `Reused` that names no place, which a whole write would replace only there -- fixtures/reject/src/lib.rs:2191:3: a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which a whole write would replace only there -- fixtures/reject/src/lib.rs:2202:2: a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which a whole write would replace only there +- fixtures/reject/src/lib.rs:2251:2: a slice or a cell of a mutable reference to `Reused` that names no place, which a whole write would replace only there +- fixtures/reject/src/lib.rs:2293:3: a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which a whole write would replace only there +- fixtures/reject/src/lib.rs:2304:2: a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which a whole write would replace only there + +
+ +
an offset of a pointer to `_`, which JS keeps as an object that knows no array it is in (3) + +- fixtures/reject/src/lib.rs:1698:56: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:1698:73: an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in +- fixtures/reject/src/lib.rs:2146:13: an offset of a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in
@@ -696,10 +726,24 @@ The type or module each refused type or path belongs to. +
`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 (2) + +- fixtures/reject/src/lib.rs:2210:5: `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/lib.rs:2211: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 + +
+ +
`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 (2) + +- fixtures/reject/src/lib.rs:2212: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/lib.rs:2213: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 + +
+
a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another (2) - fixtures/reject/src/lib.rs:540:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another -- fixtures/reject/src/lib.rs:1698:17: a cast from a pointer to `[u8; 4]` to one to `u8`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2162:13: a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another
@@ -710,6 +754,20 @@ The type or module each refused type or path belongs to. +
a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust (2) + +- fixtures/reject/src/lib.rs:2235:18: a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust +- fixtures/reject/src/lib.rs:2238:21: a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust + +
+ +
a raw or mutable slice of a byte in no array of bytes (2) + +- fixtures/reject/src/lib.rs:2221:12: a raw or mutable slice of a byte in no array of bytes +- fixtures/reject/src/lib.rs:2222:19: a raw or mutable slice of a byte in no array of bytes + +
+
a raw pointer to a place of `()`, which has no address of its own (2) - fixtures/reject/src/lib.rs:713:15: a raw pointer to a place of `()`, which has no address of its own @@ -842,33 +900,45 @@ The type or module each refused type or path belongs to.
-
a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet (1) +
`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 (1) -- fixtures/reject/src/lib.rs:209:40: a `collect` in place of `std::iter::Map, {closure@fixtures/reject/src/lib.rs:208:32: 208:40}>, std::ops::Range>, {closure@fixtures/reject/src/lib.rs:209:24: 209:32}>`, a zip std reads by index, holding values that drop, which is not supported yet +- fixtures/reject/src/lib.rs:2215: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
-
a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet (1) +
`offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays (1) -- fixtures/reject/src/lib.rs:202:8: a `skip` of `std::iter::Zip, {closure@fixtures/reject/src/lib.rs:201:27: 201:31}>, std::iter::Map, {closure@fixtures/reject/src/lib.rs:201:59: 201:63}>>`, a zip std reads by index, of items that drop, which is not supported yet +- fixtures/reject/src/lib.rs:2214:20: `offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays
-
a cast from `_` to `_` (1) +
`offset_from` of pointers to `()`, which has no bytes (1) -- fixtures/reject/src/lib.rs:68:2: a cast from `u64` to `f16` +- fixtures/reject/src/lib.rs:2157:13: `offset_from` of pointers to `()`, which has no bytes
-
a cast from `_` to `_`, which reads the bytes of one type as another (1) +
`offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in (1) -- fixtures/reject/src/lib.rs:1698:33: a cast from `*const [u8; 4]` to `*const u8`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2150:36: `offset_from` of pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in
-
a coercion of `_` to `_`, a pointer to a slice (1) +
a `collect` in place of `_`, a zip std reads by index, holding values that drop, which is not supported yet (1) -- fixtures/reject/src/lib.rs:1699:28: a coercion of `*const [u8; 4]` to `*const [u8]`, a pointer to a slice +- fixtures/reject/src/lib.rs:209:40: a `collect` in place of `std::iter::Map, {closure@fixtures/reject/src/lib.rs:208:32: 208:40}>, std::ops::Range>, {closure@fixtures/reject/src/lib.rs:209:24: 209:32}>`, a zip std reads by index, holding values that drop, which is not supported yet + +
+ +
a `skip` of `_`, a zip std reads by index, of items that drop, which is not supported yet (1) + +- fixtures/reject/src/lib.rs:202:8: a `skip` of `std::iter::Zip, {closure@fixtures/reject/src/lib.rs:201:27: 201:31}>, std::iter::Map, {closure@fixtures/reject/src/lib.rs:201:59: 201:63}>>`, a zip std reads by index, of items that drop, which is not supported yet + +
+ +
a cast from `_` to `_` (1) + +- fixtures/reject/src/lib.rs:68:2: a cast from `u64` to `f16`
@@ -884,6 +954,12 @@ The type or module each refused type or path belongs to.
+
a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust (1) + +- fixtures/reject/src/lib.rs:2233:3: a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust + +
+
a dangling pointer to `_`, a value of no bytes made of more than structs and tuples (1) - fixtures/reject/src/lib.rs:722:11: a dangling pointer to `Only`, a value of no bytes made of more than structs and tuples @@ -914,6 +990,24 @@ The type or module each refused type or path belongs to.
+
a pointer into `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2144:12: a pointer into `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+ +
a pointer into an array of `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2149:30: a pointer into an array of `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+ +
a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust (1) + +- fixtures/reject/src/lib.rs:2237:27: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust + +
+
a pointer's address, which JS has no counterpart of (1) - fixtures/reject/src/lib.rs:545:17: a pointer's address, which JS has no counterpart of @@ -944,9 +1038,27 @@ The type or module each refused type or path belongs to.
+
a slice from a pointer to `_`, which JS keeps as an object that knows no array it is in (1) + +- fixtures/reject/src/lib.rs:2148:11: a slice from a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in + +
+ +
a slice of `()`, which has no bytes, from a pointer (1) + +- fixtures/reject/src/lib.rs:2157:39: a slice of `()`, which has no bytes, from a pointer + +
+
a slice of one byte's place, which a byte slice can view only in an array of bytes (1) -- fixtures/reject/src/lib.rs:2143:2: a slice of one byte's place, which a byte slice can view only in an array of bytes +- fixtures/reject/src/lib.rs:2245:2: a slice of one byte's place, which a byte slice can view only in an array of bytes + +
+ +
a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust (1) + +- fixtures/reject/src/lib.rs:2236:6: a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust
diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 0f6dd7a56..3159f6f30 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -176,17 +176,13 @@ a raw pointer to `ByKeyTake` in `(u16, ByKeyTake)`, which the crate may put anot unsupported call to `core::str::::as_bytes_mut` at fixtures/reject/src/lib.rs:1621:13 `core::str::::get_unchecked_mut` with a mutable reference to a value JS copies, which its mapping would copy at fixtures/reject/src/lib.rs:1622:13 `std::str::from_utf8_mut` with a mutable reference to a value JS copies, which its mapping would copy at fixtures/reject/src/lib.rs:1627:20 -unsupported call to `std::ptr::copy::<[u8; 4]>` at fixtures/reject/src/lib.rs:1695:3 -unsupported call to `std::ptr::copy_nonoverlapping::<[u8; 4]>` at fixtures/reject/src/lib.rs:1696:3 +a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:1695:3 +a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:1696:3 unsupported call to `std::ptr::addr_eq::<[u8; 4], [u8; 4]>` at fixtures/reject/src/lib.rs:1697:14 comparing `*mut [u8; 4]` at fixtures/reject/src/lib.rs:1697:41 -a cast from a pointer to `[u8; 4]` to one to `u8`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:1698:17 -a cast from `*const [u8; 4]` to `*const u8`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:1698:33 -unsupported call to `std::ptr::mut_ptr::::add` at fixtures/reject/src/lib.rs:1698:56 -unsupported call to `std::ptr::const_ptr::::offset` at fixtures/reject/src/lib.rs:1698:73 -a coercion of `*const [u8; 4]` to `*const [u8]`, a pointer to a slice at fixtures/reject/src/lib.rs:1699:28 +an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:1698:56 +an offset of a pointer to `[u8; 4]`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:1698:73 hashing `*const [u8; 4]` by its address, which JS has no counterpart of at fixtures/reject/src/lib.rs:1701:5 -unsupported call to `std::ptr::const_ptr::::len` at fixtures/reject/src/lib.rs:1702:58 a raw pointer to `[u8; 3]` in an element, a payload, or a union field, which is not supported yet at fixtures/reject/src/lib.rs:1711:15 a raw pointer to `[u8; 3]` in an element, a payload, or a union field, which is not supported yet at fixtures/reject/src/lib.rs:1711:18 a raw pointer to `[u8; 2]` in an element, a payload, or a union field, which is not supported yet at fixtures/reject/src/lib.rs:1711:37 @@ -199,16 +195,40 @@ const generic types are not supported: `Block` at fixtures/reject/src/lib.rs:208 `clone_from` of `std::collections::LinkedList`, whose items' `clone_from` is their own, which is not supported yet at fixtures/reject/src/lib.rs:2103:4 `std::default::Default::default` with a mutable reference to a value JS copies, which its mapping would copy at fixtures/reject/src/lib.rs:2104:2 a mapped guard of `{closure@fixtures/reject/src/lib.rs:2136:38: 2136:50}`, which JS can't write in place at fixtures/reject/src/lib.rs:2137:10 -a slice of one byte's place, which a byte slice can view only in an array of bytes at fixtures/reject/src/lib.rs:2143:2 -a slice or a cell of a mutable reference to `Reused` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2149:2 -`Clone::clone_from` of `std::vec::Vec`, whose `clone_from` is its own, which is not supported yet at fixtures/reject/src/lib.rs:2154:4 -unsupported call to `std::mem::size_of_val::` at fixtures/reject/src/lib.rs:2159:2 -a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2165:10 -a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2173:10 -a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2181:10 -a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2191:3 -an assignment through a reference to `std::sync::LazyLock` at fixtures/reject/src/lib.rs:2196:2 -a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2202:2 +a pointer into `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2144:12 +an offset of a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2146:13 +a copy between pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2147:3 +a slice from a pointer to `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2148:11 +a pointer into an array of `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2149:30 +`offset_from` of pointers to `(u8, u8)`, which JS keeps as an object that knows no array it is in at fixtures/reject/src/lib.rs:2150:36 +`offset_from` of pointers to `()`, which has no bytes at fixtures/reject/src/lib.rs:2157:13 +a slice of `()`, which has no bytes, from a pointer at fixtures/reject/src/lib.rs:2157:39 +a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2162:13 +a raw pointer to `[u32; 3]` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2175:10 +a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2177:10 +`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 at fixtures/reject/src/lib.rs:2210:5 +`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 at fixtures/reject/src/lib.rs:2211:26 +`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 at fixtures/reject/src/lib.rs:2212: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 at fixtures/reject/src/lib.rs:2213:23 +`offset_from` between a pointer into a shared slice JS copies and one into an array of this body's own, which JS keeps in different arrays at fixtures/reject/src/lib.rs:2214:20 +`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 at fixtures/reject/src/lib.rs:2215:21 +a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2221:12 +a raw or mutable slice of a byte in no array of bytes at fixtures/reject/src/lib.rs:2222:19 +a copy reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2233:3 +a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2235:18 +a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2236:6 +a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2237:27 +a pointer reaching past a shared slice JS copies, which JS keeps apart from what holds it in Rust at fixtures/reject/src/lib.rs:2238:21 +a slice of one byte's place, which a byte slice can view only in an array of bytes at fixtures/reject/src/lib.rs:2245:2 +a slice or a cell of a mutable reference to `Reused` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2251:2 +`Clone::clone_from` of `std::vec::Vec`, whose `clone_from` is its own, which is not supported yet at fixtures/reject/src/lib.rs:2256:4 +unsupported call to `std::mem::size_of_val::` at fixtures/reject/src/lib.rs:2261:2 +a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2267:10 +a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2275:10 +a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:2283:10 +a slice or a cell of a mutable reference to `std::cell::LazyCell` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2293:3 +an assignment through a reference to `std::sync::LazyLock` at fixtures/reject/src/lib.rs:2298:2 +a slice or a cell of a mutable reference to `std::thread::LocalKey` that names no place, which a whole write would replace only there at fixtures/reject/src/lib.rs:2304:2 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:125:2 an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet at fixtures/reject/src/lib.rs:145:1 overriding `Iterator::size_hint`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:173:2 @@ -224,7 +244,7 @@ a raw pointer to `std::cell::UnsafeCell` in `InField`, which the crate may a raw pointer to `std::cell::UnsafeCell` in `InGeneric`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:1307:9 a raw pointer to `std::cell::UnsafeCell` in `InWritten`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:1311:9 a reference to `std::option::Option>`, through which a raw pointer may be made, in a variable assigned again while that pointer may point there at fixtures/reject/src/lib.rs:2093:13 -a raw pointer to `(u8, u8)` in `([Cloned; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2210:23 -a raw pointer to `(u8, u8)` in `(Dropped, (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2210:23 -a raw pointer to `(u8, u8)` in `([Greatest; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2266:13 -a raw pointer to `(u8, u8)` in `([Touched; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2210:23 +a raw pointer to `(u8, u8)` in `([Cloned; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2312:23 +a raw pointer to `(u8, u8)` in `(Dropped, (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2312:23 +a raw pointer to `(u8, u8)` in `([Greatest; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2368:13 +a raw pointer to `(u8, u8)` in `([Touched; 2], (u8, u8))`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:2312:23 diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 70a2a2b7e..f91880830 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -346,11 +346,33 @@ export function ptrSlice(p: Place, len: number): T[] { export class RawSlice { readonly ptr: Place; readonly length: number; + /** What the first read through it made, which later reads share while it spans `length`. */ + #made: T[] | ByteSlice | undefined; + /** Whether `#made` is the array itself, which spans more once it grows. */ + #whole = false; constructor(ptr: Place, length: number) { this.ptr = ptr; this.length = length; } + + /** The slice it points at, read through it, which must be there. */ + deref(): T[] { + const made = this.#made as T[] | undefined; + if (made !== undefined && (!this.#whole || made.length === this.length)) return made; + const slice = ptrSlice(this.ptr, this.length); + this.#whole = this.ptr instanceof AtIndex && slice === this.ptr.array; + return (this.#made = slice); + } + + /** The bytes it points at, read through it, `shared` as `ptrByteSlice` takes it. */ + derefBytes(this: RawSlice, shared: boolean): ByteSlice { + if (this.#made !== undefined) return this.#made as ByteSlice; + const made = ptrByteSlice(this.ptr, this.length, shared); + // A copy of a byte in no array would miss a later write to the byte. + if (this.length === 0 || (this.ptr instanceof AtIndex && this.ptr.array instanceof Uint8Array)) this.#made = made; + return made; + } } /** The raw pointer to `s`, a slice, a `[T; N]`, or a `Vec`'s storage. */ @@ -363,16 +385,6 @@ export function rawByteSliceOf(s: ByteSlice): RawSlice { return new RawSlice(byteSliceAsPtr(s), s.length); } -/** The slice `p` points at, read through it, which must be there. */ -export function rawSliceDeref(p: RawSlice): T[] { - return ptrSlice(p.ptr, p.length); -} - -/** The bytes `p` points at, read through it, `shared` as `ptrByteSlice` takes it. */ -export function rawByteSliceDeref(p: RawSlice, shared: boolean): ByteSlice { - return ptrByteSlice(p.ptr, p.length, shared); -} - /** * `slice::from_raw_parts` of `len` bytes from `p`. Only a `shared` reference to bytes in * no array of bytes may be a copy of them, since nothing writes them while it lives. diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 1d1546c5b..1f0a6fe95 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -210,12 +210,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } // A raw pointer to a slice is where it starts, read through as the slice. if let ty::RawPtr(pointee, _) = ty.kind() - && !glue && !boxed + && !glue + && !boxed && self.b.t.pointee(*pointee) == crate::lower::pointer::Pointee::Slice { e = match crate::lower::pointer::slice_of_bytes(*pointee) { - true => Expr::rt("rawByteSliceDeref", vec![e, Expr::lit("false")]), - false => Expr::rt("rawSliceDeref", vec![e]), + true => e.member("derefBytes").call(vec![Expr::lit("false")]), + false => e.member("deref").call(Vec::new()), }; } } diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 477f72fcb..e30e73933 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -81,7 +81,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // length. Pointee::Slice => match place.projection.as_slice() { [rest @ .., mir::ProjectionElem::Deref] - if Place::ty_from(place.local, rest, &self.mir.local_decls, tcx).ty.is_raw_ptr() => + if Place::ty_from(place.local, rest, &self.mir.local_decls, tcx) + .ty + .is_raw_ptr() => { let base = Place { local: place.local, @@ -266,10 +268,16 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { return self.err(span, format!("a coercion of `{from}` to a pointer to a slice")); }; let Some(len) = len.try_to_target_usize(self.tcx) else { - return self.err(span, format!("a coercion of `{from}`, of no fixed length, to a pointer to a slice")); + return self.err( + span, + format!("a coercion of `{from}`, of no fixed length, to a pointer to a slice"), + ); }; let first = crate::lower::pointer::cast(self.b, p, array, elem, span); - Expr::New(Box::new(Expr::Rt("RawSlice".into())), vec![first, Expr::lit(len.to_string())]) + Expr::New( + Box::new(Expr::Rt("RawSlice".into())), + vec![first, Expr::lit(len.to_string())], + ) } pub(super) fn const_int(&self, op: &Operand<'tcx>) -> Option { diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 08a8decfe..7b10a1eda 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -453,8 +453,8 @@ pub fn call<'tcx>( false => Expr::rt("rawSliceOf", vec![s]), }; let deref = |ty: Ty<'tcx>, p: Expr, shared: bool| match slice_of_bytes(ty) { - true => Expr::rt("rawByteSliceDeref", vec![p, Expr::lit(shared.to_string())]), - false => Expr::rt("rawSliceDeref", vec![p]), + true => p.member("derefBytes").call(vec![Expr::lit(shared.to_string())]), + false => p.member("deref").call(Vec::new()), }; let mut args = args.into_iter(); let mut arg = || args.next().expect("an argument"); From b71eb2f74fa754dfb656b94ad7bbfe0933c47831 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:21:41 -0800 Subject: [PATCH 32/34] fix(ts): fail a raw slice read past an array that shrank Co-Authored-By: Claude Opus 5.5 --- ts/runtime/rt.ts | 11 +++++++++-- ts/tests/pointers.ts | 13 +++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index f91880830..69b2430d1 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -348,6 +348,8 @@ export class RawSlice { readonly length: number; /** What the first read through it made, which later reads share while it spans `length`. */ #made: T[] | ByteSlice | undefined; + /** Where `#made` starts in the array beneath it, if anywhere. */ + #at: AtIndex | undefined; /** Whether `#made` is the array itself, which spans more once it grows. */ #whole = false; @@ -359,9 +361,14 @@ export class RawSlice { /** The slice it points at, read through it, which must be there. */ deref(): T[] { const made = this.#made as T[] | undefined; - if (made !== undefined && (!this.#whole || made.length === this.length)) return made; + if (made !== undefined) { + // Fails as a new read would once the array has shrunk past it. + this.#at?.reach(this.length); + if (!this.#whole || made.length === this.length) return made; + } const slice = ptrSlice(this.ptr, this.length); - this.#whole = this.ptr instanceof AtIndex && slice === this.ptr.array; + this.#at = this.ptr instanceof AtIndex ? elemOf(this.ptr) : undefined; + this.#whole = slice === this.#at?.array; return (this.#made = slice); } diff --git a/ts/tests/pointers.ts b/ts/tests/pointers.ts index 1f44ebf84..6295114e7 100644 --- a/ts/tests/pointers.ts +++ b/ts/tests/pointers.ts @@ -64,3 +64,16 @@ unsure(() => { unsure(() => rt.ptrCopy(dangling, rt.sliceAsPtr(vec), 1)); unsure(() => rt.ptrSlice(dangling, 1)); assert.equal(vec[0], 7); + +// A raw slice pointer read through once its `Vec` has shrunk past it, which Rust keeps +// the old values for, and one past the array, which Rust allows until a read. +const rows = [1, 2, 3, 4]; +const all = new rt.RawSlice(rt.sliceAsPtr(rows), 4); +const tail = new rt.RawSlice(rt.sliceAsPtr(rows, 1), 3); +assert.equal(all.deref()[3] + tail.deref()[2], 8); +rows.length = 3; +unsure(() => all.deref()); +unsure(() => tail.deref()); +const long = new rt.RawSlice(rt.sliceAsPtr(rows), 10); +assert.equal(long.length, 10); +unsure(() => long.deref()); From 2224ffe7aa50d01bbeb8e7a3d5357f8f0e7b8e33 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:55:38 -0800 Subject: [PATCH 33/34] fix(ts): read through NonNull::as_mut of a slice Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/elements.ts | 43 +++++++++++++++++---------- ts/fixtures/lang/src/elements.rs | 9 +++++- ts/src/lower/pointer.rs | 6 ++-- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index 9fabb2846..4e3256e54 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -349,16 +349,16 @@ export function rawSlices(data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecPush(out, rt.iterSum(rt.iterSlice(p.deref()), rt.u32Add, 0) & 0xff); emit(v, out); const bytes: rt.ByteVec = rt.byteSliceToByteVec(data); - rt.byteVecPush(bytes, 3); + rt.iterExtendBytes(bytes, rt.iterSlice(Uint8Array.of(3, 4)), undefined, true); const b: rt.RawSlice = new rt.RawSlice(rt.byteVecAsPtr(bytes), bytes.length); rt.idx(b, 0); const slice2 = b.derefBytes(false); const slice3 = b.derefBytes(false); slice3.data[slice3.start] = slice2.data[slice2.start] ^ 255; - const _66: rt.usize = rt.usizeSub(b.derefBytes(false).length, 1); - rt.idx(b, _66); + const _67: rt.usize = rt.usizeSub(b.derefBytes(false).length, 1); + rt.idx(b, _67); const slice4 = b.derefBytes(false); - rt.byteVecPush(out, slice4.data[slice4.start + _66]); + rt.byteVecPush(out, slice4.data[slice4.start + _67]); rt.byteVecPush(out, rt.byteSliceRange(b.derefBytes(false), 1).length & 0xff); rt.iterExtendBytes(out, rt.iterSlice(rt.byteVecView(bytes)), undefined, true); const short: rt.u32[] = [n as rt.u32, 1]; @@ -368,31 +368,44 @@ export function rawSlices(data: rt.ByteSlice, out: rt.ByteVec): void { const pointer = rt.sliceAsPtr(v); const nn: rt.RawSlice = new rt.RawSlice(rt.optionUnwrap(pointer === rt.NULL ? undefined : pointer), 2); rt.byteVecPush(out, nn.length & 0xff); - const _102: rt.RawSlice = nn; - rt.idx(_102, 1); - _102.deref()[1] = 11; - const _109: rt.u32[] = nn.deref(); - rt.idx(_109, 1); - rt.byteVecPush(out, _109[1] & 0xff); + const _103: rt.RawSlice = nn; + rt.idx(_103, 1); + _103.deref()[1] = 11; + const _110: rt.u32[] = nn.deref(); + rt.idx(_110, 1); + rt.byteVecPush(out, _110[1] & 0xff); const pointer2 = nn; rt.byteVecPush(out, option_mapOr$0grxkdu(pointer2.ptr === rt.NULL ? undefined : pointer2.deref(), 0, (s: rt.u32[]): rt.u32 => { rt.idx(s, 0); return s[0]; }) & 0xff); + const _121: rt.u32[] = nn.deref(); + rt.idx(_121, 0); + _121[0] = 12; + const pointer3 = rt.byteVecAsPtr(bytes); + const two: rt.RawSlice = new rt.RawSlice(rt.optionUnwrap(pointer3 === rt.NULL ? undefined : pointer3), 2); + const _132: rt.ByteSlice = two.derefBytes(false); + rt.idx(_132, 1); + _132.data[_132.start + 1] = _132.data[_132.start + 1] ^ 15; + const _140: rt.ByteSlice = two.derefBytes(true); + rt.idx(_140, 1); + rt.byteVecPush(out, _140.data[_140.start + 1]); const null_: rt.RawSlice = new rt.RawSlice(rt.NULL, 0); - const pointer3 = null_; - rt.byteVecPush(out, ((pointer3.ptr === rt.NULL ? undefined : pointer3)) === undefined ? 1 : 0); const pointer4 = null_; - rt.byteVecPush(out, ((pointer4.ptr === rt.NULL ? undefined : pointer4.deref())) === undefined ? 1 : 0); + rt.byteVecPush(out, ((pointer4.ptr === rt.NULL ? undefined : pointer4)) === undefined ? 1 : 0); + const pointer5 = null_; + rt.byteVecPush(out, ((pointer5.ptr === rt.NULL ? undefined : pointer5.deref())) === undefined ? 1 : 0); const raw: rt.RawSlice = rt.rawSliceOf([n as rt.u32, 2, 3]); rt.idx(raw, 2); raw.deref()[2] = 8; emit(raw.deref(), out); - const whole: rt.RawSlice = new rt.RawSlice(rt.sliceAsPtr(short), 2); + const whole2: rt.RawSlice = new rt.RawSlice(rt.sliceAsPtr(short), 2); const ofRef: rt.RawSlice = rt.rawSliceOf(rt.sliceRange(v, 1, 3)); - rt.byteVecPush(out, rt.usizeAdd(whole.length, ofRef.length) & 0xff); + rt.byteVecPush(out, rt.usizeAdd(whole2.length, ofRef.length) & 0xff); rt.idx(ofRef, 1); rt.byteVecPush(out, ofRef.deref()[1] & 0xff); + emit(v, out); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteVecView(bytes)), undefined, true); } /** diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index 1f6e3a0db..f5c4dbc3a 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -422,7 +422,7 @@ pub fn raw_slices(data: &[u8], out: &mut Vec) { } emit(&v, out); let mut bytes = data.to_vec(); - bytes.push(3); + bytes.extend([3, 4]); let b = ptr::slice_from_raw_parts_mut(bytes.as_mut_ptr(), bytes.len()); unsafe { (*b)[0] ^= 0xff; @@ -441,6 +441,11 @@ pub fn raw_slices(data: &[u8], out: &mut Vec) { (*nn.as_ptr())[1] = 11; out.push(nn.as_ref()[1] as u8); out.push(nn.as_ptr().as_mut().map_or(0, |s| s[0]) as u8); + let mut whole = nn; + whole.as_mut()[0] = 12; + let mut two = NonNull::slice_from_raw_parts(NonNull::new(bytes.as_mut_ptr()).unwrap(), 2); + two.as_mut()[1] ^= 0x0f; + out.push(two.as_ref()[1]); } let null = ptr::slice_from_raw_parts_mut(ptr::null_mut::(), 0); out.push(NonNull::new(null).is_none() as u8); @@ -454,6 +459,8 @@ pub fn raw_slices(data: &[u8], out: &mut Vec) { let of_ref: *const [u32] = &v[1..3]; out.push((whole.len() + of_ref.len()) as u8); out.push(unsafe { (*of_ref)[1] } as u8); + emit(&v, out); + out.extend(&bytes); } /// Empty slices made from pointers into no array, a dangling one and one off null, each diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 7b10a1eda..b0949a3b5 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -509,6 +509,9 @@ pub fn call<'tcx>( ("ptr" | "NonNull", "len") => arg().member("length"), ("ptr" | "NonNull", "is_empty") => arg().member("length").binary("===", Expr::lit("0")), ("ptr", "is_null") => arg().binary("===", Expr::Rt("NULL".into())), + ("NonNull", "as_ref" | "as_mut") if kind(b, pointee()) == Some(Pointee::Slice) => { + deref(pointee(), arg(), name == "as_ref") + } ("NonNull", "as_mut") if bytes(b, pointee()) => to_reference(arg()), // The same pointer, as another type of the same pointee. ("ptr", "cast_const" | "cast_mut") @@ -560,9 +563,6 @@ pub fn call<'tcx>( Box::new(deref(pointee(), p, name == "as_ref")), ) } - ("NonNull", "as_ref" | "as_mut") if kind(b, pointee()) == Some(Pointee::Slice) => { - deref(pointee(), arg(), name == "as_ref") - } // `Option<&T>`: a shared reference to a value JS copies is the value. ("ptr", "as_ref" | "as_mut") => { let p = b.spill(arg(), "pointer", out); From 4c5c4a72bcb631b7ff000f0cada2080781a28b58 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 11:35:07 -0800 Subject: [PATCH 34/34] fix(ts): keep the pointer of an empty slice of a lone byte Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-fma-allowance.md | 6 ++++-- ts/README.md | 2 +- ts/fixtures/lang/expected/elements.ts | 16 ++++++++++++---- ts/fixtures/lang/src/elements.rs | 11 +++++++++++ ts/runtime/rt.ts | 3 ++- ts/src/lower/mir/origin.rs | 9 ++++++--- 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/quest/m3/ts-fma-allowance.md b/quest/m3/ts-fma-allowance.md index 257e965a4..3e33b25ea 100644 --- a/quest/m3/ts-fma-allowance.md +++ b/quest/m3/ts-fma-allowance.md @@ -11,5 +11,7 @@ translation is the exactly rounded one and native is a last place from it Once [rust-lang/compiler-builtins#1358](https://github.com/rust-lang/compiler-builtins/pull/1358) ships in the compiler-builtins that the pinned nightly in `ts/rust-toolchain.toml` -builds, delete `fma.ts`, its test `ts/tests/fma.ts`, and `within`'s call to it, so -any difference fails again, and delete this quest. +builds, delete `fma.ts` and every use of it: `caller.ts`'s import and `within`'s +call, its copy in `ts/tests/fixtures.rs`'s `lang_differential`, and its test +`ts/tests/fma.ts` with `fma_allowance` in `ts/tests/runtime.rs`, so any difference +fails again, and delete this quest. diff --git a/ts/README.md b/ts/README.md index 057125412..994be55cc 100644 --- a/ts/README.md +++ b/ts/README.md @@ -844,7 +844,7 @@ they need. ## Not yet -Byte reinterpretation, pointer addresses, pointer arithmetic outside an array of values JS copies (other than wrapping arithmetic on a null or dangling pointer), raw allocation, and unions whose +Byte reinterpretation, pointer addresses, pointer arithmetic on a value JS keeps as an object (other than wrapping arithmetic on a null or dangling pointer), raw allocation, and unions whose fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than `&dyn Debug`, `&dyn Display`, `dyn Future`, `dyn Any`, `dyn Fn*`, and the translated crates' traits' behind `&`, `&mut`, `Box`, or `Arc` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `dyn Error`, binds to diff --git a/ts/fixtures/lang/expected/elements.ts b/ts/fixtures/lang/expected/elements.ts index 4e3256e54..179126a4b 100644 --- a/ts/fixtures/lang/expected/elements.ts +++ b/ts/fixtures/lang/expected/elements.ts @@ -413,8 +413,8 @@ export function rawSlices(data: rt.ByteSlice, out: rt.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: rt.ByteSlice, out: rt.ByteVec): void { - const _130: (a0: rt.Place) => rt.u8 = (p: rt.Place): rt.u8 => { - return rt.ptrWrappingAdd(p, -1, 1, 1) === rt.NULL ? 1 : 0; + const _130: (a0: rt.Place) => rt.u8 = (p2: rt.Place): rt.u8 => { + return rt.ptrWrappingAdd(p2, -1, 1, 1) === rt.NULL ? 1 : 0; }; const k: rt.usize = rt.usizeAdd(rt.u8Rem(option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0), 4) as rt.usize, 1); const wide: rt.Place = rt.DANGLING; @@ -426,8 +426,8 @@ export function emptyParts(data: rt.ByteSlice, out: rt.ByteVec): void { break; } const back: rt.usize = _23[1]; - const null_: (a0: rt.Place) => rt.u8 = (p2: rt.Place): rt.u8 => { - return rt.ptrWrappingAdd(p2, -back, 2, 2) === rt.NULL ? 1 : 0; + const null_: (a0: rt.Place) => rt.u8 = (p3: rt.Place): rt.u8 => { + return rt.ptrWrappingAdd(p3, -back, 2, 2) === rt.NULL ? 1 : 0; }; const _32: [rt.u16[], rt.u16[]] = rt.sliceSplitAt(_23[0], 0); const part = rt.sliceAsPtr(_32[0]); @@ -489,6 +489,14 @@ export function emptyParts(data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecPush(out, rt.ptrWrappingAdd(raw.ptr, -k, 2, 2) === rt.NULL ? 1 : 0); const part22 = rt.ptrWrappingAdd(new rt.RawSlice(byte, 0).ptr, 0, 1, 1); rt.byteVecPush(out, _130(part22)); + let lone: rt.u8 = data.length & 0xff; + const p: rt.Place = rt.place(() => lone, (value) => lone = value); + rt.byteVecPush(out, rt.ptrOffsetFrom(rt.byteSliceAsPtr(rt.ptrByteSlice(p, 0, true)), p) & 0xff); + rt.byteVecPush(out, rt.ptrOffsetFrom(rt.byteSliceAsPtr(rt.ptrByteSlice(rt.ptrAdd(p, 1), 0, true)), p) & 0xff); + let held: rt.u8 = lone; + const q: rt.Place = rt.place(() => held, (value2) => held = value2); + const r: rt.ByteSlice = rt.ptrByteSlice(q, 0, false); + rt.byteVecPush(out, rt.u8Add(r.length & 0xff, rt.ptrOffsetFrom(rt.byteSliceAsPtr(r), q) & 0xff)); } export function emit(values: rt.u32[], out: rt.ByteVec): void { diff --git a/ts/fixtures/lang/src/elements.rs b/ts/fixtures/lang/src/elements.rs index f5c4dbc3a..67e9d7f27 100644 --- a/ts/fixtures/lang/src/elements.rs +++ b/ts/fixtures/lang/src/elements.rs @@ -503,6 +503,17 @@ pub fn empty_parts(data: &[u8], out: &mut Vec) { out.push(raw.len() as u8); out.push((raw as *const u16).wrapping_sub(k).is_null() as u8); out.push(null((ptr::slice_from_raw_parts(byte, 0) as *const u8).wrapping_add(0))); + // None of a lone byte, at it and past it, shared and mutable. + let lone = data.len() as u8; + let p = &raw const lone; + unsafe { + out.push(slice::from_raw_parts(p, 0).as_ptr().offset_from(p) as u8); + out.push(slice::from_raw_parts(p.add(1), 0).as_ptr().offset_from(p) as u8); + } + let mut held = lone; + let q = &raw mut held; + let r = unsafe { slice::from_raw_parts_mut(q, 0) }; + out.push(r.len() as u8 + unsafe { r.as_ptr().offset_from(q) } as u8); } /// Functions of the crate's named as std's pointer functions. diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index dda54a454..fe9ad5f75 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -398,7 +398,8 @@ export function rawByteSliceOf(s: ByteSlice): RawSlice { * no array of bytes may be a copy of them, since nothing writes them while it lives. */ export function ptrByteSlice(p: Place, len: number, shared: boolean): ByteSlice { - if (len === 0 && !(p instanceof AtIndex)) { + // None from a pointer into no array of bytes, such as a lone byte's, keeps it. + if (len === 0 && !(p instanceof AtIndex && p.array instanceof Uint8Array)) { const none = new Uint8Array(0); EMPTY.set(none, p); return new ByteSlice(none, 0, 0); diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index a820404e3..0896b6618 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -102,7 +102,8 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { | ("NonNull", "slice_from_raw_parts") => { let o = self.origin(arg(0)?)?; let byte = matches!(pointee.kind(), ty::Uint(ty::UintTy::U8)); - if byte && name != "from_raw_parts" && matches!(o.array, Array::Lone(_)) { + 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. @@ -265,9 +266,11 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { let elem = match (owner.as_str(), name.as_str(), container.kind()) { ("raw", "from_raw_parts" | "from_raw_parts_mut", _) => { let o = self.origin(&args[0].node)?; - // A shared slice of a byte in no array is a copy of it, as no `Uint8Array` can share it. + // A shared slice of a byte in no array is a copy of it, as no `Uint8Array` can + // share it, unless empty, which keeps the pointer. + let len = self.const_int(&args[1].node); return match (byte(container), o.array) { - (true, Array::Lone(_)) => copy(self.const_int(&args[1].node)), + (true, Array::Lone(_)) if len != Some(0) => copy(len), _ => Some(o), }; }