diff --git a/quest/m3/ts-unsafe.md b/quest/m3/ts-unsafe.md index 63185fe50..76e4bd5ee 100644 --- a/quest/m3/ts-unsafe.md +++ b/quest/m3/ts-unsafe.md @@ -33,10 +33,12 @@ none of its own `unsafe`. 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 - 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. + hashbrown's buckets and kio's `log.rs` reach it. +- Decided with the maintainer on 2026-11-01, typed allocation: memory from `alloc` for a + `Layout::array::(n)`, or an equal layout of `T`'s size and alignment, cast to `*mut T` + is a fresh JS array of `T`s, which `realloc` copies and `dealloc` frees. Untyped and + byte allocation stay refused. smallvec spills to it, and `Vec::from_raw_parts` of it is + left. - 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. @@ -51,6 +53,10 @@ 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. +- `offset_from` between byte pointers into two views of one buffer, such as a slice's + `as_mut_ptr` and its `iter_mut`'s first element: each is an `rt.AtIndex` into its own + `Uint8Array`, so it fails loudly at run time where Rust measures the distance. Comparing + by buffer and byte offset closes it. - `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 a31c0026d..3c813461e 100644 --- a/ts/README.md +++ b/ts/README.md @@ -79,7 +79,7 @@ and each type a module of its own beside it, holding the type and its functions from. `rt.ts` beside them is the runtime: what std does that JS does not, with iterators in `rt/iter.ts`, `str`'s searches in `rt/str.ts`, the ASCII methods of `u8`, `char`, `[u8]`, and `str` in `rt/ascii.ts`, collections in `rt/collections.ts`, hashing in `rt/hash.ts`, ownership, atomics, and async in -`rt/task.ts`, `f64`'s and `f32`'s methods in `rt/float.ts`, and `Duration` in `rt/time.ts`. What translates from std's own MIR, +`rt/task.ts`, `f64`'s and `f32`'s methods in `rt/float.ts`, `Duration` in `rt/time.ts`, and `Layout` and memory from `alloc` in `rt/alloc.ts`. What translates from std's own MIR, `Option`'s, `Result`'s, and `Poll`'s methods, `==` of std's errors, `==` and ordering of a `Result` and a `Poll`, `cmp::max` and `min`, and the `str::parse` and `from_str_radix` of an integer or a `NonZero` and `bool`'s `str::parse`, goes in a module under its crate's name, as `core/option.ts` holds `map$1cf8u0y`. A dependency's modules go @@ -118,6 +118,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `PeekMut` | `rt.PeekMut`, the `rt.Place` of a heap's greatest element, which sifts it down as it drops once `DerefMut` lent it | | `Ordering`, `DefaultHasher`, `RandomState` | `rt.Ordering`, which is `-1`, `0`, or `1`; `rt.Hasher`; `rt.RandomState`, the keys of the `DefaultHasher` it builds, never changed once made | | `Duration` | `rt.Duration`: `{ secs, nanos }`, std's whole seconds as an `rt.u64` and the nanoseconds past them, never changed once made | +| `Layout`, `alloc::alloc` | `rt.Layout`: `{ size, align }`, never changed once made, checked as std's constructors check it; `alloc` gives an `rt.Block` of its bytes, which a cast of its pointer to `*mut T` the body shows came from `alloc` or `realloc` makes a JS array of `T`s, values JS copies, as an `rt.AtIndex` of its first; a cast back to `*mut u8` only as what `dealloc` or `realloc` takes, which free the array or copy it into a new one; its bytes, read, written, or cast to any other type, are refused, and fail at run time through a pointer from elsewhere | | closure | an arrow function, with `[Symbol.dispose]` if it owns a value that drops, and what makes its clone if a clone can't be itself | | `fn` pointer, function item as a value | the function, as a closure is typed: a `()` result is `undefined`, where a declared function returns `void` | | `&dyn Trait`, `&mut dyn Trait`, `Box`, `Arc` | an object with the trait's methods, its supertraits' included: `shape.area$Shape()`; behind an `Arc`, an `rt.ArcDyn` sharing the count of the `Arc` it was made from | @@ -852,7 +853,7 @@ they need. ## Not yet -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 +Byte reinterpretation, pointer addresses, pointer arithmetic on a value JS keeps as an object (other than wrapping arithmetic on a null or dangling pointer), memory from `alloc` other than an array of values JS copies, 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*`, `dyn Buf`, 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/core/any.Any.ts b/ts/fixtures/lang/expected/core/any.Any.ts index 9346c60c5..1641c4302 100644 --- a/ts/fixtures/lang/expected/core/any.Any.ts +++ b/ts/fixtures/lang/expected/core/any.Any.ts @@ -3,7 +3,7 @@ import * as rt from "../rt.ts"; import type * as Square from "../dyns.Square.ts"; import type * as Tri from "../dyns.Tri.ts"; -import type * as Fault from "../fmt.Fault.ts"; +import type * as fmt_Fault from "../fmt.Fault.ts"; export type Any = object; @@ -32,9 +32,9 @@ export class of$dyns_Tri { } export class of$fmt_Fault { - v: Fault.Fault; + v: fmt_Fault.Fault; - constructor(v: Fault.Fault) { + constructor(v: fmt_Fault.Fault) { this.v = v; } } @@ -96,9 +96,9 @@ export class ofPlace$dyns_Tri { } export class ofPlace$fmt_Fault { - v: rt.Place; + v: rt.Place; - constructor(v: rt.Place) { + constructor(v: rt.Place) { this.v = v; } } diff --git a/ts/fixtures/lang/expected/core/bool.ts b/ts/fixtures/lang/expected/core/bool.ts index 500833f8d..579a88bd3 100644 --- a/ts/fixtures/lang/expected/core/bool.ts +++ b/ts/fixtures/lang/expected/core/bool.ts @@ -98,6 +98,13 @@ export function thenSome$1jqdi5p(self: boolean, t: rt.Duration): rt.Duration | u return t; } +export function thenSome$0j2n934(self: boolean, t: [rt.u8, rt.u16]): [rt.u8, rt.u16] | undefined { + if (!self) { + return undefined; + } + return t; +} + export function thenSome$03chm1q(self: boolean, t: [rt.u8, rt.u8]): [rt.u8, rt.u8] | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/expected/core/option.ts b/ts/fixtures/lang/expected/core/option.ts index cc5889673..065930542 100644 --- a/ts/fixtures/lang/expected/core/option.ts +++ b/ts/fixtures/lang/expected/core/option.ts @@ -26,6 +26,7 @@ import * as defaults_Made from "../defaults.Made.ts"; import type * as dyns_Shape from "../dyns.Shape.ts"; import * as edits_Named from "../edits.Named.ts"; import type * as Ticked from "../generics.Ticked.ts"; +import type * as heap_Fault from "../heap.Fault.ts"; import type * as hints_Unit from "../hints.Unit.ts"; import type * as Badge from "../iters.Badge.ts"; import type * as Quarter from "../iters.Quarter.ts"; @@ -1470,6 +1471,13 @@ export function mapOr$0f73bb7(self: [rt.u8, [rt.usize, rt.ByteSlice]] | undefine return f(self); } +export function mapOr$0hi5qhf(self: [rt.u8, rt.u16] | undefined, default_: rt.u8, f: (a0: [rt.u8, rt.u16]) => rt.u8): rt.u8 { + if (self === undefined) { + return default_; + } + return f(self); +} + export function mapOr$16umieg(self: [rt.usize, string] | undefined, default_: rt.usize, f: (a0: [rt.usize, string]) => rt.usize): rt.usize { if (self === undefined) { return default_; @@ -1519,6 +1527,13 @@ export function mapOr$1m2xzon(self: rt.u8 | undefined, default_: boolean, f: (a0 return f(self); } +export function mapOr$1qo6t0j(self: rt.u8 | undefined, default_: rt.u32, _: (a0: rt.u8) => rt.u32): rt.u32 { + if (self === undefined) { + return default_; + } + return self as rt.u32; +} + export function mapOr$18ph0j9(self: undefined | rt.None, default_: rt.u8, f: (a0: undefined) => rt.u8): rt.u8 { if (self === rt.None) { return default_; @@ -1561,6 +1576,13 @@ export function okOr$1b6nkwr(self: rt.char | undefined, err: rt.u8): rt.Result | undefined, err: heap_Fault.Fault): rt.Result, heap_Fault.Fault> { + if (self === undefined) { + return rt.Err(err); + } + return rt.Ok(self); +} + export function okOr$0gtm15y(self: rt.ByteSlice | undefined, err: rt.u8 | undefined): rt.Result { if (self === undefined) { return rt.Err(err); @@ -1624,6 +1646,13 @@ export function okOr$0g9lvh7(self: rt.u8 | undefined, _: undefined): rt.Result { + if (self === undefined) { + return rt.Err(err); + } + return rt.Ok(self); +} + export function okOrElse$0awp3vn(self: rt.ByteVec | undefined, err: () => string): rt.Result { if (self === undefined) { return rt.Err(err()); diff --git a/ts/fixtures/lang/expected/core/result.ts b/ts/fixtures/lang/expected/core/result.ts index c684cb71a..1009403ba 100644 --- a/ts/fixtures/lang/expected/core/result.ts +++ b/ts/fixtures/lang/expected/core/result.ts @@ -13,6 +13,7 @@ import { eq$1horntk as float_parse_eq$1horntk } from "../core/num/float_parse.ts import { eq$1v93ipo as str_error_eq$1v93ipo, eq$1f3e51k as str_error_eq$1f3e51k } from "../core/str/error.ts"; import { cmp$175ka5q as poll_cmp$175ka5q } from "../core/task/poll.ts"; import { eq$0dhyfbb as time_eq$0dhyfbb } from "../core/time.ts"; +import type * as heap_Fault from "../heap.Fault.ts"; import type * as locks_Tally from "../locks.Tally.ts"; import * as Part from "../orders.Part.ts"; import type * as returns_Error from "../returns.Error.ts"; @@ -746,6 +747,13 @@ export function mapOrElse$0jxx67n(self: rt.Result, default_: (a0: return default_(self.error); } +export function mapErr$0fesv1f(self: rt.Result, op: (a0: rt.LayoutError) => heap_Fault.Fault): rt.Result { + if (self.ok) { + return self as rt.Result; + } + return rt.Err(op(self.error)); +} + export function mapErr$12w1s5x(self: rt.Result, _: (a0: rt.u8) => rt.u16): rt.Result { if (self.ok) { return self as rt.Result; diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 4616ae1b2..652578196 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -32,6 +32,7 @@ import { ints, strings, derived, written as fmt_written, counts as fmt_counts, r import { folds } from "./folds.ts"; import { methods, runs } from "./generics.ts"; import { values as hashes_values, random, bytes as hashes_bytes, own as hashes_own } from "./hashes.ts"; +import { spills, remnants, layouts } from "./heap.ts"; import { hints, reserves } from "./hints.ts"; import { holds } from "./holds.ts"; import { u8Op, u16Op, u32Op, u64Op, u128Op, usizeOp, i8Op, i16Op, i32Op, i64Op, i128Op, isizeOp, i8Neg, i16Neg, i32Neg, i64Neg, i128Neg, isizeNeg, cast as ints_cast, convert, parses as ints_parses, widenings } from "./ints.ts"; @@ -293,18 +294,24 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) 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], ["heap::spills", (_2204: rt.u8, data104: rt.ByteSlice, out105: rt.ByteVec): undefined => { + spills(data104, out105); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["heap::layouts", (_2205: rt.u8, data105: rt.ByteSlice, out106: rt.ByteVec): undefined => { + layouts(data105, out106); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["heap::remnants", (_2206: rt.u8, data106: rt.ByteSlice, out107: rt.ByteVec): undefined => { + remnants(data106, out107); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::raw_slices", (_2207: rt.u8, data107: rt.ByteSlice, out108: rt.ByteVec): undefined => { + rawSlices(data107, out108); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["elements::empty_parts", (_2208: rt.u8, data108: rt.ByteSlice, out109: rt.ByteVec): undefined => { + emptyParts(data108, out109); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::own", (_2209: rt.u8, data109: rt.ByteSlice, out110: rt.ByteVec): undefined => { + hashes_own(data109, out110); }] 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], ["rings::slices", (a0160: rt.u8, a1160: rt.ByteSlice, a2160: rt.ByteVec): undefined => { rings_slices(a0160, a1160.data, a1160.start, a1160.length, a2160); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::objects", (_2207: rt.u8, data107: rt.ByteSlice, out108: rt.ByteVec): undefined => { - fmt_objects(data107, out108); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::objects", (_2210: rt.u8, data110: rt.ByteSlice, out111: rt.ByteVec): undefined => { + fmt_objects(data110, out111); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["sorts::sorts", (a0161: rt.u8, a1161: rt.ByteSlice, a2161: rt.ByteVec): undefined => { sorts(a0161, a1161.data, a1161.start, a1161.length, a2161); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["sorts::disorders", disorders as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["sorts::btrees", (a0163: rt.u8, a1163: rt.ByteSlice, a2163: rt.ByteVec): undefined => { diff --git a/ts/fixtures/lang/expected/heap.Fault.ts b/ts/fixtures/lang/expected/heap.Fault.ts new file mode 100644 index 000000000..4e6910d82 --- /dev/null +++ b/ts/fixtures/lang/expected/heap.Fault.ts @@ -0,0 +1,17 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export function fmt$fmt_Debug(self: Fault, f: rt.Formatter): rt.Result { + if (self.tag === "Overflow") { + return rt.fmtWriteStr(f, "Overflow"); + } + const _6: string = "Alloc"; + const _7: string = "layout"; + const _8: rt.Fmt = (f: rt.Formatter) => rt.fmtLayoutDebug(self.layout, f); + return rt.debugStructFinish(rt.debugStructField(rt.fmtDebugStruct(f, _6), _7, _8)); +} + +export type Fault = { tag: "Overflow" } | { tag: "Alloc"; layout: rt.Layout }; + +//# sourceMappingURL=heap.Fault.ts.map diff --git a/ts/fixtures/lang/expected/heap.Spill.ts b/ts/fixtures/lang/expected/heap.Spill.ts new file mode 100644 index 000000000..d843584cc --- /dev/null +++ b/ts/fixtures/lang/expected/heap.Spill.ts @@ -0,0 +1,203 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { copy$Option_tuple2_u8_u16 } from "./lib.ts"; +import { okOr$1rxv64q as option_okOr$1rxv64q } from "./core/option.ts"; +import { layoutArray$Option_tuple2_u8_u16, layoutArray$sync_Arc_u8, layoutArray$u32, deallocate$Option_tuple2_u8_u16, deallocate$sync_Arc_u8, deallocate$u32 } from "./heap.ts"; +import type * as heap_Fault from "./heap.Fault.ts"; + +/** A spilled vector's storage: `len` of `cap` values written into memory from `alloc`. */ +export class Spill { + ptr: rt.Ptr; + cap: rt.usize; + len: rt.usize; + + constructor(ptr: rt.Ptr, cap: rt.usize, len: rt.usize) { + this.ptr = ptr; + this.cap = cap; + this.len = len; + } +} + +export function new$Option_tuple2_u8_u16(cap: rt.usize): rt.Result, heap_Fault.Fault> { + const _2: rt.Result = layoutArray$Option_tuple2_u8_u16(cap); + if (_2.ok) { + const layout: rt.Layout = _2.value; + const pointer = rt.alloc(layout); + const _8: rt.Result, heap_Fault.Fault> = option_okOr$1rxv64q(pointer === rt.NULL ? undefined : pointer, { tag: "Alloc", layout } as heap_Fault.Fault); + if (_8.ok) { + return rt.Ok(new Spill(rt.allocAs(_8.value, 6, "std::option::Option<(u8, u16)>"), cap, 0)); + } + return _8; + } + return _2; +} + +export function new$sync_Arc_u8(cap: rt.usize): rt.Result>, heap_Fault.Fault> { + const _2: rt.Result = layoutArray$sync_Arc_u8(cap); + if (_2.ok) { + const layout: rt.Layout = _2.value; + const pointer = rt.alloc(layout); + const _8: rt.Result, heap_Fault.Fault> = option_okOr$1rxv64q(pointer === rt.NULL ? undefined : pointer, { tag: "Alloc", layout } as heap_Fault.Fault); + if (_8.ok) { + return rt.Ok(new Spill(rt.allocAs(_8.value, 4, "std::sync::Arc"), cap, 0)); + } + return _8; + } + return _2; +} + +export function new$u32(cap: rt.usize): rt.Result, heap_Fault.Fault> { + const _2: rt.Result = layoutArray$u32(cap); + if (_2.ok) { + const layout: rt.Layout = _2.value; + const pointer = rt.alloc(layout); + const _8: rt.Result, heap_Fault.Fault> = option_okOr$1rxv64q(pointer === rt.NULL ? undefined : pointer, { tag: "Alloc", layout } as heap_Fault.Fault); + if (_8.ok) { + return rt.Ok(new Spill(rt.allocAs(_8.value, 4, "u32"), cap, 0)); + } + return _8; + } + return _2; +} + +/** Grows or shrinks to `cap`, dropping what no longer fits. */ +export function resize$Option_tuple2_u8_u16(self: Spill<[rt.u8, rt.u16] | undefined>, cap: rt.usize): rt.Result { + while (self.len > cap) { + self.len = rt.usizeSub(self.len, 1); + rt.ptrAdd(self.ptr as rt.Place<[rt.u8, rt.u16] | undefined>, self.len); + } + const _11: rt.Result = layoutArray$Option_tuple2_u8_u16(self.cap); + if (_11.ok) { + const _18: rt.Result = layoutArray$Option_tuple2_u8_u16(cap); + if (_18.ok) { + const layout: rt.Layout = _18.value; + const pointer = rt.realloc(rt.allocBytes(self.ptr as rt.Place<[rt.u8, rt.u16] | undefined>, 6, "std::option::Option<(u8, u16)>"), layout.size); + const _30: rt.Result, heap_Fault.Fault> = option_okOr$1rxv64q(pointer === rt.NULL ? undefined : pointer, { tag: "Alloc", layout } as heap_Fault.Fault); + if (_30.ok) { + const _29: rt.Place<[rt.u8, rt.u16] | undefined> = rt.allocAs(_30.value, 6, "std::option::Option<(u8, u16)>"); + (self.ptr as rt.Place<[rt.u8, rt.u16] | undefined>) = _29; + self.cap = cap; + return rt.OK; + } + return _30; + } + return _18; + } + return _11; +} + +/** Grows or shrinks to `cap`, dropping what no longer fits. */ +export function resize$sync_Arc_u8(self: Spill>, cap: rt.usize): rt.Result { + while (self.len > cap) { + self.len = rt.usizeSub(self.len, 1); + rt.ptrAdd(self.ptr as rt.Place>, self.len).v[Symbol.dispose](); + } + const _11: rt.Result = layoutArray$sync_Arc_u8(self.cap); + if (_11.ok) { + const _18: rt.Result = layoutArray$sync_Arc_u8(cap); + if (_18.ok) { + const layout: rt.Layout = _18.value; + const pointer = rt.realloc(rt.allocBytes(self.ptr as rt.Place>, 4, "std::sync::Arc"), layout.size); + const _30: rt.Result, heap_Fault.Fault> = option_okOr$1rxv64q(pointer === rt.NULL ? undefined : pointer, { tag: "Alloc", layout } as heap_Fault.Fault); + if (_30.ok) { + const _29: rt.Place> = rt.allocAs(_30.value, 4, "std::sync::Arc"); + (self.ptr as rt.Place>) = _29; + self.cap = cap; + return rt.OK; + } + return _30; + } + return _18; + } + return _11; +} + +/** Grows or shrinks to `cap`, dropping what no longer fits. */ +export function resize$u32(self: Spill, cap: rt.usize): rt.Result { + while (self.len > cap) { + self.len = rt.usizeSub(self.len, 1); + rt.ptrAdd(self.ptr as rt.Place, self.len); + } + const _11: rt.Result = layoutArray$u32(self.cap); + if (_11.ok) { + const _18: rt.Result = layoutArray$u32(cap); + if (_18.ok) { + const layout: rt.Layout = _18.value; + const pointer = rt.realloc(rt.allocBytes(self.ptr as rt.Place, 4, "u32"), layout.size); + const _30: rt.Result, heap_Fault.Fault> = option_okOr$1rxv64q(pointer === rt.NULL ? undefined : pointer, { tag: "Alloc", layout } as heap_Fault.Fault); + if (_30.ok) { + const _29: rt.Place = rt.allocAs(_30.value, 4, "u32"); + (self.ptr as rt.Place) = _29; + self.cap = cap; + return rt.OK; + } + return _30; + } + return _18; + } + return _11; +} + +export function push$Option_tuple2_u8_u16(self: Spill<[rt.u8, rt.u16] | undefined>, value: [rt.u8, rt.u16] | undefined): rt.Result { + value = copy$Option_tuple2_u8_u16(value); + if (self.len === self.cap) { + const _6: rt.Result = resize$Option_tuple2_u8_u16(self, rt.usizeAdd(rt.usizeMul(self.cap, 2), 1)); + if (!_6.ok) { + return _6; + } + } + rt.ptrAdd(self.ptr as rt.Place<[rt.u8, rt.u16] | undefined>, self.len).v = value; + self.len = rt.usizeAdd(self.len, 1); + return rt.OK; +} + +export function push$sync_Arc_u8(self: Spill>, value: rt.Arc): rt.Result { + if (self.len === self.cap) { + const _6: rt.Result = resize$sync_Arc_u8(self, rt.usizeAdd(rt.usizeMul(self.cap, 2), 1)); + if (!_6.ok) { + value[Symbol.dispose](); + return _6; + } + } + rt.ptrAdd(self.ptr as rt.Place>, self.len).v = value; + self.len = rt.usizeAdd(self.len, 1); + return rt.OK; +} + +export function push$u32(self: Spill, value: rt.u32): rt.Result { + if (self.len === self.cap) { + const _6: rt.Result = resize$u32(self, rt.usizeAdd(rt.usizeMul(self.cap, 2), 1)); + if (!_6.ok) { + return _6; + } + } + rt.ptrAdd(self.ptr as rt.Place, self.len).v = value; + self.len = rt.usizeAdd(self.len, 1); + return rt.OK; +} + +export function asSlice$Option_tuple2_u8_u16(self: Spill<[rt.u8, rt.u16] | undefined>): Array<[rt.u8, rt.u16] | undefined> { + return rt.ptrSlice(self.ptr as rt.Place<[rt.u8, rt.u16] | undefined>, self.len); +} + +export function asSlice$u32(self: Spill): rt.u32[] { + return rt.ptrSlice(self.ptr as rt.Place, self.len); +} + +export function drop$ops_Drop_Option_tuple2_u8_u16(self: Spill<[rt.u8, rt.u16] | undefined>): void { + new rt.RawSlice(self.ptr as rt.Place<[rt.u8, rt.u16] | undefined>, self.len); + deallocate$Option_tuple2_u8_u16(self.ptr as rt.Place<[rt.u8, rt.u16] | undefined>, self.cap); +} + +export function drop$ops_Drop_sync_Arc_u8(self: Spill>): void { + rt.dropAll(new rt.RawSlice(self.ptr as rt.Place>, self.len).deref()); + deallocate$sync_Arc_u8(self.ptr as rt.Place>, self.cap); +} + +export function drop$ops_Drop_u32(self: Spill): void { + new rt.RawSlice(self.ptr as rt.Place, self.len); + deallocate$u32(self.ptr as rt.Place, self.cap); +} + +//# sourceMappingURL=heap.Spill.ts.map diff --git a/ts/fixtures/lang/expected/heap.ts b/ts/fixtures/lang/expected/heap.ts new file mode 100644 index 000000000..64ea6128c --- /dev/null +++ b/ts/fixtures/lang/expected/heap.ts @@ -0,0 +1,191 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { drop$heap_Spill_Option_tuple2_u8_u16, drop$heap_Spill_sync_Arc_u8, drop$heap_Spill_u32 } from "./lib.ts"; +import { thenSome$0j2n934 } from "./core/bool.ts"; +import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, mapOr$0hi5qhf as option_mapOr$0hi5qhf, mapOr$1qo6t0j as option_mapOr$1qo6t0j, okOr$169x1m6 as option_okOr$169x1m6 } from "./core/option.ts"; +import { mapErr$0fesv1f as result_mapErr$0fesv1f } from "./core/result.ts"; +import * as heap_Fault from "./heap.Fault.ts"; +import * as Spill from "./heap.Spill.ts"; + +export function layoutArray$Option_tuple2_u8_u16(n: rt.usize): rt.Result { + const _2: rt.Result = option_okOr$169x1m6(rt.usizeCheckedMul(6, n), { tag: "Overflow" } as heap_Fault.Fault); + if (_2.ok) { + return result_mapErr$0fesv1f(rt.layoutFromSizeAlign(_2.value, 2), (_22: rt.LayoutError): heap_Fault.Fault => { + return { tag: "Overflow" } as heap_Fault.Fault; + }); + } + return _2; +} + +export function layoutArray$sync_Arc_u8(n: rt.usize): rt.Result { + const _2: rt.Result = option_okOr$169x1m6(rt.usizeCheckedMul(4, n), { tag: "Overflow" } as heap_Fault.Fault); + if (_2.ok) { + return result_mapErr$0fesv1f(rt.layoutFromSizeAlign(_2.value, 4), (_22: rt.LayoutError): heap_Fault.Fault => { + return { tag: "Overflow" } as heap_Fault.Fault; + }); + } + return _2; +} + +export function layoutArray$u32(n: rt.usize): rt.Result { + const _2: rt.Result = option_okOr$169x1m6(rt.usizeCheckedMul(4, n), { tag: "Overflow" } as heap_Fault.Fault); + if (_2.ok) { + return result_mapErr$0fesv1f(rt.layoutFromSizeAlign(_2.value, 4), (_22: rt.LayoutError): heap_Fault.Fault => { + return { tag: "Overflow" } as heap_Fault.Fault; + }); + } + return _2; +} + +export function deallocate$Option_tuple2_u8_u16(ptr: rt.Place<[rt.u8, rt.u16] | undefined>, cap: rt.usize): void { + rt.resultUnwrap(layoutArray$Option_tuple2_u8_u16(cap), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + rt.dealloc(rt.allocBytes(ptr, 6, "std::option::Option<(u8, u16)>")); +} + +export function deallocate$sync_Arc_u8(ptr: rt.Place>, cap: rt.usize): void { + rt.resultUnwrap(layoutArray$sync_Arc_u8(cap), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + rt.dealloc(rt.allocBytes(ptr, 4, "std::sync::Arc")); +} + +export function deallocate$u32(ptr: rt.Place, cap: rt.usize): void { + rt.resultUnwrap(layoutArray$u32(cap), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + rt.dealloc(rt.allocBytes(ptr, 4, "u32")); +} + +/** + * Values pushed into, and kept or dropped by resizing, storage of `u32`s, of `Arc`s, + * whose strong counts show each drop, and of `Option` pairs. + */ +export function spills(data: rt.ByteSlice, out: rt.ByteVec): void { + const shared: rt.Arc = new rt.Arc(7); + const ints: Spill.Spill = rt.resultUnwrap(Spill.new$u32(1), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + const arcs: Spill.Spill> = rt.resultUnwrap(Spill.new$sync_Arc_u8(2), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + const pairs: Spill.Spill<[rt.u8, rt.u16] | undefined> = rt.resultUnwrap(Spill.new$Option_tuple2_u8_u16(1), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + for (const b of rt.byteSliceView(data)) { + const _17: rt.u8 = rt.u8Rem(b, 4); + if (_17 === 0) { + rt.resultUnwrap(Spill.push$u32(ints, rt.u32Mul(b as rt.u32, 1000)), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + } else if (_17 === 1) { + const _26: rt.Arc = rt.arcClone(shared); + rt.resultUnwrap(Spill.push$sync_Arc_u8(arcs, _26), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + } else if (_17 === 2) { + const _30: [rt.u8, rt.u16] | undefined = thenSome$0j2n934((b & 8) !== 0, [b, (((b as rt.u16)) << 4) & 0xffff] as [rt.u8, rt.u16]); + rt.resultUnwrap(Spill.push$Option_tuple2_u8_u16(pairs, _30), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + } else { + const cap: rt.usize = rt.usizeAdd(rt.usizeRem(b >> 2 as rt.usize, 5), 1); + rt.resultUnwrap(Spill.resize$u32(ints, cap), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + rt.resultUnwrap(Spill.resize$sync_Arc_u8(arcs, cap), (value: heap_Fault.Fault, f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(value, f)); + } + rt.byteVecPush(out, shared.strong & 0xff); + } + rt.iterExtendBytes(out, rt.iterFlatMap(rt.iterSlice(Spill.asSlice$u32(ints)), (x: rt.u32): Uint8Array => { + return rt.u32ToBeBytes(x).reverse(); + }, (value: Uint8Array) => rt.iterSlice(value), false, 4), undefined, true); + rt.byteVecPush(out, ints.cap & 0xff); + rt.iterExtendBytes(out, rt.iterMap(rt.iterSlice(Spill.asSlice$Option_tuple2_u8_u16(pairs)), (p: [rt.u8, rt.u16] | undefined): rt.u8 => { + return option_mapOr$0hi5qhf(p, 0, (_2: [rt.u8, rt.u16]): rt.u8 => { + return _2[0] ^ (_2[1] & 0xff); + }); + }), undefined, true); + drop$heap_Spill_sync_Arc_u8(arcs); + rt.byteVecPush(out, shared.strong & 0xff); + drop$heap_Spill_Option_tuple2_u8_u16(pairs); + drop$heap_Spill_u32(ints); + shared[Symbol.dispose](); +} + +/** + * Storage of `MaybeUninit`s read and freed as what each holds, memory with bytes past its + * last whole value, which `realloc` grows by too few bytes to hold one more, and pointers + * freed through `cast`. + */ +export function remnants(data: rt.ByteSlice, out: rt.ByteVec): void { + const pointer = rt.alloc(rt.resultUnwrap(rt.layoutArray(rt.usizeAdd(data.length, 1), 4, 4), rt.fmtLayoutErrorDebug)); + const slots: rt.Place = rt.allocAs(rt.optionUnwrap(pointer === rt.NULL ? undefined : pointer), 4, "u32"); + const iter: rt.Iter<[rt.usize, rt.u8]> = rt.iterEnumerate(rt.iterSlice(rt.byteSliceView(data))); + while (true) { + const _16: [rt.usize, rt.u8] | rt.Done = iter.next(); + if (_16 === rt.DONE) { + break; + } + const i: rt.usize = _16[0]; + const b: rt.u8 = _16[1]; + rt.ptrAdd(slots, i).v = (((((b as rt.u32)) << 8) >>> 0) | 1) >>> 0; + } + const words: rt.Place = slots; + rt.iterExtendBytes(out, rt.iterFlatMap(rt.iterSlice(rt.ptrSlice(words, data.length)), (x: rt.u32): Uint8Array => { + return rt.u32ToBeBytes(x).reverse(); + }, (value: Uint8Array) => rt.iterSlice(value), false, 4), undefined, true); + rt.dealloc(rt.allocBytes(words, 4, "u32")); + const pointer2 = rt.alloc(rt.resultUnwrap(rt.layoutFromSizeAlign(9, 4), rt.fmtLayoutErrorDebug)); + const pair: rt.Place = rt.allocAs(rt.optionUnwrap(pointer2 === rt.NULL ? undefined : pointer2), 4, "u32"); + const first: rt.u32 = option_mapOr$1qo6t0j(rt.byteSliceFirst(data), 0, (a0: rt.u8): rt.u32 => { + return a0 as rt.u32; + }); + pair.v = first; + rt.ptrAdd(pair, 1).v = rt.u32Mul(first, 3); + const pointer3 = rt.realloc(rt.allocBytes(pair, 4, "u32"), 11); + const grown2: rt.Place = rt.allocAs(rt.optionUnwrap(pointer3 === rt.NULL ? undefined : pointer3), 4, "u32"); + rt.iterExtendBytes(out, rt.iterFlatMap(rt.iterSlice([grown2.v, rt.ptrAdd(grown2, 1).v]), (x2: rt.u32): Uint8Array => { + return rt.u32ToBeBytes(x2).reverse(); + }, (value: Uint8Array) => rt.iterSlice(value), false, 4), undefined, true); + rt.dealloc(rt.allocBytes(grown2, 4, "u32")); + const one: rt.Layout = { size: 1, align: 1 }; + const pointer4 = rt.alloc(one); + const byte: rt.Place = rt.allocAs(rt.optionUnwrap(pointer4 === rt.NULL ? undefined : pointer4), 1, "u8"); + byte.v = first & 0xff; + const pointer5 = rt.realloc(rt.allocBytes(byte, 1, "u8"), 2); + const two2: rt.Place = rt.allocAs(rt.optionUnwrap(pointer5 === rt.NULL ? undefined : pointer5), 1, "u8"); + rt.ptrAdd(two2, 1).v = 7; + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(two2.v, rt.ptrAdd(two2, 1).v)), undefined, true); + const _107: rt.Place = rt.allocBytes(two2, 1, "u8"); + rt.resultUnwrap(rt.layoutArray(2, 1, 1), rt.fmtLayoutErrorDebug); + rt.dealloc(_107); +} + +/** + * `Layout`s made of sizes and alignments up to wasm32's `isize::MAX`, and of arrays of + * `u64`s and of values of no bytes, and what `Debug` and `Display` write of each. + */ +export function layouts(data: rt.ByteSlice, out: rt.ByteVec): void { + let align!: rt.usize; + const iter: rt.Iter = rt.byteSliceChunks(data, 5); + while (true) { + const _6: rt.ByteSlice | rt.Done = iter.next(); + if (_6 === rt.DONE) { + break; + } + const word: Uint8Array = rt.bytes(4); + const part = rt.min(_6.length, 4); + rt.byteSliceCopyFromSlice(rt.byteSliceRange(rt.byteSliceFrom(word), 0, part), rt.byteSliceRange(_6, 0, rt.min(_6.length, 4))); + const size: rt.usize = rt.u32FromBeBytes(word.slice().reverse()) as rt.usize; + const b: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceGet(_6, 4), 0); + if ((b & 128) === 0) { + align = b as rt.usize; + } else { + align = rt.usizeShl(1, rt.u8Rem(b, 32)); + } + const made: rt.Result = rt.layoutFromSizeAlign(size, align); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtResult(made, f, rt.fmtLayoutDebug, rt.fmtLayoutErrorDebug)]; + rt.iterExtendBytes(out, rt.strBytes(rt.fmtFormat([args2[0]])), undefined, true); + if (!made.ok) { + rt.iterExtendBytes(out, rt.strBytes(rt.fmtToString((f: rt.Formatter) => rt.fmtLayoutErrorDisplay(rt.LAYOUT_ERROR, f))), undefined, true); + } + const part3 = rt.layoutArray(rt.usizeShr(size, rt.u8Rem(b, 8)), 8, 8); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtResult(part3, f, rt.fmtLayoutDebug, rt.fmtLayoutErrorDebug)]; + rt.iterExtendBytes(out, rt.strBytes(rt.fmtFormat([args4[0]])), undefined, true); + const unit: rt.Layout = rt.resultUnwrap(rt.layoutArray(size, 0, 1), rt.fmtLayoutErrorDebug); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(unit.size & 0xff, unit.align & 0xff)), undefined, true); + } + const _92: heap_Fault.Fault = { tag: "Alloc", layout: { size: 16, align: 8 } }; + const _94: rt.Layout = { size: 4, align: 4 }; + const args6: rt.Fmt[] = [(f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(_92, f), (f: rt.Formatter) => rt.fmtLayoutDebug(_94, f)]; + rt.iterExtendBytes(out, rt.strBytes(rt.fmtFormat([args6[0], " ", args6[1]])), undefined, true); + rt.byteVecPush(out, 8 & 0xff); + const arg = { tag: "Overflow" } as heap_Fault.Fault; + const args8: rt.Fmt[] = [(f: rt.Formatter) => heap_Fault.fmt$fmt_Debug(arg, f)]; + rt.iterExtendBytes(out, rt.strBytes(rt.fmtFormat([args8[0]])), undefined, true); +} + +//# sourceMappingURL=heap.ts.map diff --git a/ts/fixtures/lang/expected/lib.ts b/ts/fixtures/lang/expected/lib.ts index f7c6197e7..de4833739 100644 --- a/ts/fixtures/lang/expected/lib.ts +++ b/ts/fixtures/lang/expected/lib.ts @@ -23,6 +23,7 @@ import * as Fixed from "./generics.Fixed.ts"; import type * as generics_Run from "./generics.Run.ts"; import type * as Ticked from "./generics.Ticked.ts"; import * as Wrap from "./generics.Wrap.ts"; +import * as Spill from "./heap.Spill.ts"; import type * as Bumped from "./iters.Bumped.ts"; import type * as joins_Logged from "./joins.Logged.ts"; import type * as Hop from "./keys.Hop.ts"; @@ -182,6 +183,10 @@ export function copy$Option_tuple2_u64_u64(v: [rt.u64, rt.u64] | undefined): [rt return v === undefined ? v : copy$tuple2_u64_u64(v); } +export function copy$Option_tuple2_u8_u16(v: [rt.u8, rt.u16] | undefined): [rt.u8, rt.u16] | undefined { + return v === undefined ? v : copy$tuple2_u8_u16(v); +} + export function copy$Option_tuple2_u8_u8(v: [rt.u8, rt.u8] | undefined): [rt.u8, rt.u8] | undefined { return v === undefined ? v : copy$tuple2_u8_u8(v); } @@ -1205,6 +1210,18 @@ export function drop$generics_Run_generics_Ticked(_1: generics_Run.Run): void { + Spill.drop$ops_Drop_Option_tuple2_u8_u16(_1); +} + +export function drop$heap_Spill_sync_Arc_u8(_1: Spill.Spill>): void { + Spill.drop$ops_Drop_sync_Arc_u8(_1); +} + +export function drop$heap_Spill_u32(_1: Spill.Spill): void { + Spill.drop$ops_Drop_u32(_1); +} + export function drop$pointers_List_String(_1: List.List): void { List.drop$ops_Drop_String(_1); } diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index de239c44b..11c33625a 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -4,7 +4,7 @@ use crate::{ args, arrays, ascii, atomics, borrows, bounds, boxes, capacity, captures, cells, chars, chunks, clones, collections, combinators, conversions, counts, cows, cycles, defaults, durations, dyns, edits, elements, errors, - fields, floats, fmt, folds, generics, hashes, hints, holds, ints, iters, joins, keys, lengths, lists, locals, + fields, floats, fmt, folds, generics, hashes, heap, hints, holds, ints, iters, joins, keys, lengths, lists, locals, locks, methods, nested, objects, orders, pointers, refs, results, returns, rings, saturates, separated, shapes, singles, slices, sorts, splits, statics, strs, tuples, utf8, wrappers, wraps, zeros, }; @@ -293,6 +293,9 @@ 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)), + ("heap::spills", |_, data, out| heap::spills(data, out)), + ("heap::layouts", |_, data, out| heap::layouts(data, out)), + ("heap::remnants", |_, data, out| heap::remnants(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)), diff --git a/ts/fixtures/lang/src/heap.rs b/ts/fixtures/lang/src/heap.rs new file mode 100644 index 000000000..b8404e603 --- /dev/null +++ b/ts/fixtures/lang/src/heap.rs @@ -0,0 +1,185 @@ +//! Memory from `alloc`, typed by a cast of its pointer, grown and shrunk by `realloc`, and +//! freed by `dealloc`, as smallvec's spilled storage is, and the `Layout`s it is laid out by. + +use std::alloc::{self, Layout}; +use std::mem::{self, MaybeUninit}; +use std::ptr::{self, NonNull}; +use std::sync::Arc; + +#[derive(Debug)] +enum Fault { + Overflow, + Alloc { layout: Layout }, +} + +const WORD: Layout = Layout::new::(); + +fn layout_array(n: usize) -> Result { + let size = mem::size_of::().checked_mul(n).ok_or(Fault::Overflow)?; + Layout::from_size_align(size, mem::align_of::()).map_err(|_| Fault::Overflow) +} + +unsafe fn deallocate(ptr: NonNull, cap: usize) { + let layout = layout_array::(cap).unwrap(); + unsafe { alloc::dealloc(ptr.as_ptr() as *mut u8, layout) } +} + +/// A spilled vector's storage: `len` of `cap` values written into memory from `alloc`. +struct Spill { + ptr: NonNull, + cap: usize, + len: usize, +} + +impl Spill { + fn new(cap: usize) -> Result { + let layout = layout_array::(cap)?; + let ptr = NonNull::new(unsafe { alloc::alloc(layout) }) + .ok_or(Fault::Alloc { layout })? + .cast(); + Ok(Spill { ptr, cap, len: 0 }) + } + + /// Grows or shrinks to `cap`, dropping what no longer fits. + fn resize(&mut self, cap: usize) -> Result<(), Fault> { + while self.len > cap { + self.len -= 1; + unsafe { ptr::drop_in_place(self.ptr.as_ptr().add(self.len)) }; + } + let old = layout_array::(self.cap)?; + let layout = layout_array::(cap)?; + let p = unsafe { alloc::realloc(self.ptr.as_ptr() as *mut u8, old, layout.size()) }; + self.ptr = NonNull::new(p).ok_or(Fault::Alloc { layout })?.cast(); + self.cap = cap; + Ok(()) + } + + fn push(&mut self, value: T) -> Result<(), Fault> { + if self.len == self.cap { + self.resize(self.cap * 2 + 1)?; + } + unsafe { self.ptr.as_ptr().add(self.len).write(value) }; + self.len += 1; + Ok(()) + } + + fn as_slice(&self) -> &[T] { + unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) } + } +} + +impl Drop for Spill { + fn drop(&mut self) { + unsafe { + ptr::drop_in_place(ptr::slice_from_raw_parts_mut(self.ptr.as_ptr(), self.len)); + deallocate(self.ptr, self.cap); + } + } +} + +/// Values pushed into, and kept or dropped by resizing, storage of `u32`s, of `Arc`s, +/// whose strong counts show each drop, and of `Option` pairs. +pub fn spills(data: &[u8], out: &mut Vec) { + let shared = Arc::new(7u8); + let mut ints = Spill::::new(1).unwrap(); + let mut arcs = Spill::>::new(2).unwrap(); + let mut pairs = Spill::>::new(1).unwrap(); + for &b in data { + match b % 4 { + 0 => ints.push(u32::from(b) * 1000).unwrap(), + 1 => arcs.push(shared.clone()).unwrap(), + 2 => pairs.push((b & 8 != 0).then_some((b, u16::from(b) << 4))).unwrap(), + _ => { + let cap = usize::from(b >> 2) % 5 + 1; + ints.resize(cap).unwrap(); + arcs.resize(cap).unwrap(); + } + } + out.push(Arc::strong_count(&shared) as u8); + } + out.extend(ints.as_slice().iter().flat_map(|x| x.to_le_bytes())); + out.push(ints.cap as u8); + out.extend(pairs.as_slice().iter().map(|p| p.map_or(0, |(a, b)| a ^ b as u8))); + drop(arcs); + out.push(Arc::strong_count(&shared) as u8); +} + +/// Storage of `MaybeUninit`s read and freed as what each holds, memory with bytes past its +/// last whole value, which `realloc` grows by too few bytes to hold one more, and pointers +/// freed through `cast`. +pub fn remnants(data: &[u8], out: &mut Vec) { + let layout = Layout::array::>(data.len() + 1).unwrap(); + let slots = NonNull::new(unsafe { alloc::alloc(layout) }) + .unwrap() + .cast::>(); + for (i, &b) in data.iter().enumerate() { + unsafe { slots.as_ptr().add(i).write(MaybeUninit::new(u32::from(b) << 8 | 1)) }; + } + let words = slots.cast::(); + let read = unsafe { std::slice::from_raw_parts(words.as_ptr(), data.len()) }; + out.extend(read.iter().flat_map(|x| x.to_le_bytes())); + unsafe { alloc::dealloc(words.as_ptr() as *mut u8, layout) }; + + let odd = Layout::from_size_align(9, 4).unwrap(); + let pair = NonNull::new(unsafe { alloc::alloc(odd) }).unwrap().cast::(); + let first = data.first().copied().map_or(0, u32::from); + unsafe { + pair.as_ptr().write(first); + pair.as_ptr().add(1).write(first * 3); + } + let grown = unsafe { alloc::realloc(pair.as_ptr() as *mut u8, odd, 11) }; + let grown = NonNull::new(grown).unwrap().cast::(); + let kept = unsafe { [grown.as_ptr().read(), grown.as_ptr().add(1).read()] }; + out.extend(kept.iter().flat_map(|x| x.to_le_bytes())); + unsafe { alloc::dealloc(grown.as_ptr() as *mut u8, Layout::from_size_align_unchecked(11, 4)) }; + + let one = Layout::new::(); + let byte = NonNull::new(unsafe { alloc::alloc(one) }) + .unwrap() + .cast::>(); + unsafe { byte.as_ptr().write(MaybeUninit::new(first as u8)) }; + let two = unsafe { alloc::realloc(byte.as_ptr().cast::(), one, 2) }; + let two = NonNull::new(two).unwrap().cast::>(); + unsafe { + two.as_ptr().add(1).write(MaybeUninit::new(7)); + out.extend([ + two.as_ptr().read().assume_init(), + two.as_ptr().add(1).read().assume_init(), + ]); + alloc::dealloc(two.cast::().as_ptr(), Layout::array::(2).unwrap()); + } +} + +/// `Layout`s made of sizes and alignments up to wasm32's `isize::MAX`, and of arrays of +/// `u64`s and of values of no bytes, and what `Debug` and `Display` write of each. +pub fn layouts(data: &[u8], out: &mut Vec) { + for part in data.chunks(5) { + let mut word = [0u8; 4]; + word[..part.len().min(4)].copy_from_slice(&part[..part.len().min(4)]); + let size = u32::from_le_bytes(word) as usize; + let b = part.get(4).copied().unwrap_or(0); + let align = match b & 0x80 { + 0 => usize::from(b), + _ => 1 << (b % 32), + }; + let made = Layout::from_size_align(size, align); + out.extend(format!("{made:?}").bytes()); + if let Err(e) = made { + out.extend(e.to_string().bytes()); + } + out.extend(format!("{:?}", Layout::array::(size >> (b % 8))).bytes()); + let unit = Layout::array::<()>(size).unwrap(); + out.extend([unit.size() as u8, unit.align() as u8]); + } + out.extend( + format!( + "{:?} {WORD:?}", + Fault::Alloc { + layout: Layout::new::<(u8, u64)>() + } + ) + .bytes(), + ); + out.push(mem::align_of::() as u8); + out.extend(format!("{:?}", Fault::Overflow).bytes()); +} diff --git a/ts/fixtures/lang/src/lib.rs b/ts/fixtures/lang/src/lib.rs index 3c23eab9c..1b03b87e0 100644 --- a/ts/fixtures/lang/src/lib.rs +++ b/ts/fixtures/lang/src/lib.rs @@ -19,7 +19,8 @@ //! 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, pointers -//! into arrays moved, measured, and copied between, tuples of +//! into arrays moved, measured, and copied between, memory from `alloc` typed by a cast of +//! its pointer, grown, shrunk, and freed, and the `Layout`s it is laid out by, tuples of //! collections collected and extended by their items, zero-sized values, which MIR //! never assigns, with std's impls of them, functions generic over an array's length, and //! std's conversions of a reference: clones into it, searches, sizes, slices, cells, and @@ -72,6 +73,7 @@ mod folds; mod generics; pub mod harness; mod hashes; +mod heap; mod hints; mod holds; mod ints; diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 4ae4e6f58..b856a5fa6 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,7 +1,7 @@ # Census of `reject-fixture` -- Refusals: 249 -- Function instances: 296 +- Refusals: 259 +- Function instances: 299 - Generic functions no instance reached, lowered over their type parameters: 13 ## By construct @@ -10,14 +10,17 @@ | ---: | --- | --- | | 50 | a raw pointer to `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:586:10 | | 23 | a raw pointer to `_` in a variable assigned again while it may point there | fixtures/reject/src/lib.rs:594:10 | -| 20 | unsupported call to `_` | fixtures/reject/src/lib.rs:160:8 | +| 18 | 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:1315: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:1633:18 | +| 6 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:533:27 | | 5 | `_` with a mutable reference to a value JS copies, which its mapping would copy | fixtures/reject/src/lib.rs:1213: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:657:10 | +| 5 | memory from `alloc` cast to pointers to `_` and to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:2324:7 | | 4 | a variant of `_`, which is not supported: either is the same JS string | fixtures/reject/src/lib.rs:309: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 byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as | fixtures/reject/src/lib.rs:546:3 | | 3 | a copy between pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:1688: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:2244:2 | @@ -30,7 +33,6 @@ | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | fixtures/reject/src/lib.rs:442: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:2203: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:2205: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:533:27 | | 2 | a coercion to `_`, which is not supported yet | fixtures/reject/src/lib.rs:433: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:2228:18 | | 2 | a raw or mutable slice of a byte in no array of bytes | fixtures/reject/src/lib.rs:2214:12 | @@ -104,17 +106,18 @@ The type or module each refused type or path belongs to. | ---: | --- | --- | | 39 | `std::cell::UnsafeCell` | fixtures/reject/src/lib.rs:665:15 | | 27 | `(u32, u32)` | fixtures/reject/src/lib.rs:586:10 | +| 15 | `u32` | fixtures/reject/src/lib.rs:533:27 | | 11 | `(u8, u8)` | fixtures/reject/src/lib.rs:1250:31 | | 9 | `[u8; 4]` | fixtures/reject/src/lib.rs:459:26 | -| 7 | `u32` | fixtures/reject/src/lib.rs:533:27 | +| 8 | `u8` | fixtures/reject/src/lib.rs:253:6 | | 6 | `f16` | fixtures/reject/src/lib.rs:7:14 | | 6 | `std::borrow::Cow` | fixtures/reject/src/lib.rs:309:2 | | 5 | `Gap` | fixtures/reject/src/lib.rs:886:10 | | 5 | `Option` | fixtures/reject/src/lib.rs:16:15 | +| 5 | `i32` | fixtures/reject/src/lib.rs:2324:7 | | 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:417:4 | -| 4 | `u8` | fixtures/reject/src/lib.rs:253:6 | | 3 | `Mid` | fixtures/reject/src/lib.rs:1333:34 | | 3 | `Outer` | fixtures/reject/src/lib.rs:1333:34 | | 3 | `Reused` | fixtures/reject/src/lib.rs:21:4 | @@ -142,14 +145,13 @@ The type or module each refused type or path belongs to. | 2 | `std::mem::MaybeUninit` | fixtures/reject/src/lib.rs:900:19 | | 2 | `std::ops::Range` | fixtures/reject/src/lib.rs:160:8 | | 2 | `std::option::Option` | fixtures/reject/src/lib.rs:347:2 | -| 2 | `std::std` | fixtures/reject/src/lib.rs:545: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:412:17 | -| 1 | `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2343:2 | -| 1 | `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2327:2 | -| 1 | `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2361:13 | -| 1 | `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2392:2 | +| 1 | `(Dropped, (u8, u8))` | fixtures/reject/src/lib.rs:2383:2 | +| 1 | `([Cloned; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2367:2 | +| 1 | `([Greatest; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2401:13 | +| 1 | `([Touched; 2], (u8, u8))` | fixtures/reject/src/lib.rs:2432:2 | | 1 | `(std::vec::Vec, Tens)` | fixtures/reject/src/lib.rs:1250:31 | | 1 | `(u16, ByKeyReplace)` | fixtures/reject/src/lib.rs:1599:55 | | 1 | `(u16, ByKeySet)` | fixtures/reject/src/lib.rs:1591:51 | @@ -196,6 +198,7 @@ The type or module each refused type or path belongs to. | 1 | `Escaped` | fixtures/reject/src/lib.rs:1050:43 | | 1 | `FusedIterator` | fixtures/reject/src/lib.rs:193:1 | | 1 | `Fused` | fixtures/reject/src/lib.rs:706:70 | +| 1 | `Halves` | fixtures/reject/src/lib.rs:2311:16 | | 1 | `HashMap` | fixtures/reject/src/lib.rs:253:6 | | 1 | `Hits` | fixtures/reject/src/lib.rs:698:21 | | 1 | `InField` | fixtures/reject/src/lib.rs:1296:9 | @@ -256,7 +259,7 @@ The type or module each refused type or path belongs to. | 1 | `std::ops::RangeTo` | fixtures/reject/src/lib.rs:962:11 | | 1 | `std::ptr` | fixtures/reject/src/lib.rs:1690:14 | | 1 | `std::slice::IterMut` | fixtures/reject/src/lib.rs:744:15 | -| 1 | `std::std::Layout` | fixtures/reject/src/lib.rs:543:15 | +| 1 | `std::std` | fixtures/reject/src/lib.rs:2338:13 | | 1 | `std::sync::LazyLock` | fixtures/reject/src/lib.rs:2291:2 | | 1 | `std::sync::Weak` | fixtures/reject/src/lib.rs:433:40 | | 1 | `std::task::Poll` | fixtures/reject/src/lib.rs:77:34 | @@ -273,11 +276,14 @@ The type or module each refused type or path belongs to. | 4 | a variant of `_`, which is not supported: either is the same JS string | `std::borrow::Cow` | fixtures/reject/src/lib.rs:309:2 | | 3 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u32, u32)`, `Gap` | fixtures/reject/src/lib.rs:886:10 | | 3 | a reference through which a raw pointer may be made into `_` in `_`, which the crate may put another object in place of | `Mid`, `Outer` | fixtures/reject/src/lib.rs:1333:34 | +| 3 | memory from `alloc` cast to pointers to `_` and to `_`, which reads the bytes of one type as another | `i32`, `u32` | fixtures/reject/src/lib.rs:2325:11 | | 3 | unsupported type `_` | `f16` | fixtures/reject/src/lib.rs:7:14 | | 2 | `_` is not supported: its `None` and `Some` would both be `rt.None` | `Option` | fixtures/reject/src/lib.rs:16:15 | | 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:442:23 | +| 2 | 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:533:27 | +| 2 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u8`, `u32` | fixtures/reject/src/lib.rs:2310:15 | | 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:1688: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:900:19 | | 2 | a raw pointer to `_` in a variable assigned again while it may point there | `[u8; 4]` | fixtures/reject/src/lib.rs:1711:26 | @@ -289,6 +295,7 @@ The type or module each refused type or path belongs to. | 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:1691:56 | | 2 | comparing pointers to `_`, each a place JS makes of its own | `u32` | fixtures/reject/src/lib.rs:574:2 | +| 2 | memory from `alloc` cast to pointers to `_` and to `_`, which reads the bytes of one type as another | `u32`, `i32` | fixtures/reject/src/lib.rs:2324:7 | | 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:412:17 | | 1 | `Buf::copy_to_bytes` of `_`, a value JS copies, which is not supported | `Blank` | fixtures/reject/src/lib.rs:739:2 | @@ -318,7 +325,7 @@ The type or module each refused type or path belongs to. | 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 a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `[u32; 2]` | fixtures/reject/src/lib.rs:2155: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:533:27 | +| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u8`, `Halves` | fixtures/reject/src/lib.rs:2311:16 | | 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:469:30 | | 1 | a coercion to `_`, which is not supported yet | `std::sync::Weak` | fixtures/reject/src/lib.rs:433:40 | @@ -338,10 +345,10 @@ 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:830: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:647: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:665: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:2343: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:2327: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:2361: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:2392: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:2383: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:2367: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:2401: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:2432: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:1599: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:1591: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:1606:52 | @@ -443,9 +450,7 @@ The type or module each refused type or path belongs to. | 1 | unsupported call to `_` | `std::ops::Range::is_empty` | fixtures/reject/src/lib.rs:967:9 | | 1 | unsupported call to `_` | `std::ops::RangeTo::contains` | fixtures/reject/src/lib.rs:962:11 | | 1 | unsupported call to `_` | `std::ptr::addr_eq` | fixtures/reject/src/lib.rs:1690:14 | -| 1 | unsupported call to `_` | `std::std::Layout::new` | fixtures/reject/src/lib.rs:543:15 | -| 1 | unsupported call to `_` | `std::std::alloc` | fixtures/reject/src/lib.rs:545:11 | -| 1 | unsupported call to `_` | `std::std::dealloc` | fixtures/reject/src/lib.rs:548:3 | +| 1 | unsupported call to `_` | `std::std::alloc_zeroed` | fixtures/reject/src/lib.rs:2338:13 | | 1 | unsupported call to `_` | `std::str::as_bytes_mut` | fixtures/reject/src/lib.rs:1614:13 | | 1 | unsupported call to `_` | `std::str::parse` | fixtures/reject/src/lib.rs:417:4 | | 1 | unsupported type `_` from another crate | `std::net::AddrParseError` | fixtures/reject/src/lib.rs:416:32 | @@ -501,10 +506,10 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:1599:55: a raw pointer to `ByKeyReplace` in `(u16, ByKeyReplace)`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:1606:52: a raw pointer to `ByKeyTake` in `(u16, ByKeyTake)`, which the crate may put another object in place of - fixtures/reject/src/lib.rs:2170:10: a raw pointer to `[u32; 3]` in `Lanes`, which the crate may put another object in place of -- fixtures/reject/src/lib.rs:2327: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:2343: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:2361: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:2392: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:2367: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:2383: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:2401: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:2432:2: a raw pointer to `(u8, u8)` in `([Touched; 2], (u8, u8))`, which the crate may put another object in place of @@ -536,7 +541,7 @@ The type or module each refused type or path belongs to. -
unsupported call to `_` (20) +
unsupported call to `_` (18) - 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::` @@ -547,9 +552,6 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:417:4: unsupported call to `core::str::::parse::` - fixtures/reject/src/lib.rs:516:2: unsupported call to `::eq` - fixtures/reject/src/lib.rs:516:17: unsupported call to `std::any::TypeId::of::` -- fixtures/reject/src/lib.rs:543:15: unsupported call to `std::alloc::Layout::new::` -- fixtures/reject/src/lib.rs:545:11: unsupported call to `std::alloc::alloc` -- fixtures/reject/src/lib.rs:548:3: unsupported call to `std::alloc::dealloc` - fixtures/reject/src/lib.rs:744:15: unsupported call to ` as std::iter::Iterator>::is_sorted` - fixtures/reject/src/lib.rs:962:11: unsupported call to `std::ops::RangeTo::::contains::` - fixtures/reject/src/lib.rs:962:32: unsupported call to ` as std::ops::RangeBounds>::contains::` @@ -558,6 +560,7 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:1614:13: unsupported call to `core::str::::as_bytes_mut` - fixtures/reject/src/lib.rs:1690:14: unsupported call to `std::ptr::addr_eq::<[u8; 4], [u8; 4]>` - fixtures/reject/src/lib.rs:2254:2: unsupported call to `std::mem::size_of_val::` +- fixtures/reject/src/lib.rs:2338:13: unsupported call to `std::alloc::alloc_zeroed`
@@ -601,6 +604,17 @@ 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 (6) + +- fixtures/reject/src/lib.rs:533: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:2155:13: a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2310:15: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2311:16: a cast from a pointer to `u8` to one to `Halves`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2313:15: 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:2338:12: a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another + +
+
`_` with a mutable reference to a value JS copies, which its mapping would copy (5) - fixtures/reject/src/lib.rs:1213:22: `std::iter::once` with a mutable reference to a value JS copies, which its mapping would copy @@ -621,6 +635,16 @@ The type or module each refused type or path belongs to.
+
memory from `alloc` cast to pointers to `_` and to `_`, which reads the bytes of one type as another (5) + +- fixtures/reject/src/lib.rs:2324:7: memory from `alloc` cast to pointers to `u32` and to `i32`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2325:11: memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2327:17: memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2328:21: memory from `alloc` cast to pointers to `u32` and to `i32`, which reads the bytes of one type as another +- fixtures/reject/src/lib.rs:2330:17: memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another + +
+
a variant of `_`, which is not supported: either is the same JS string (4) - fixtures/reject/src/lib.rs:309:2: a variant of `std::borrow::Cow<'_, str>`, which is not supported: either is the same JS string @@ -647,6 +671,14 @@ The type or module each refused type or path belongs to.
+
a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as (3) + +- fixtures/reject/src/lib.rs:546:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as +- fixtures/reject/src/lib.rs:547:11: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as +- fixtures/reject/src/lib.rs:2309:3: a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as + +
+
a copy between pointers to `_`, which JS keeps as an object that knows no array it is in (3) - fixtures/reject/src/lib.rs:1688:3: a copy between pointers to `[u8; 4]`, which JS keeps as an object that knows no array it is in @@ -737,13 +769,6 @@ 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:533: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:2155:13: a cast from a pointer to `u32` to one to `[u32; 2]`, which reads the bytes of one type as another - -
-
a coercion to `_`, which is not supported yet (2) - fixtures/reject/src/lib.rs:433:40: a coercion to `std::sync::Weak`, which is not supported yet diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 451b30565..171074256 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -71,9 +71,8 @@ unsupported call to `std::any::TypeId::of::` at fixtures/reject/src/lib.rs:5 unsupported call to `::eq` at fixtures/reject/src/lib.rs:516:2 a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:533:27 a pointer's address, which JS has no counterpart of at fixtures/reject/src/lib.rs:538:17 -unsupported call to `std::alloc::Layout::new::` at fixtures/reject/src/lib.rs:543:15 -unsupported call to `std::alloc::alloc` at fixtures/reject/src/lib.rs:545:11 -unsupported call to `std::alloc::dealloc` at fixtures/reject/src/lib.rs:548:3 +a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as at fixtures/reject/src/lib.rs:546:3 +a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as at fixtures/reject/src/lib.rs:547:11 a raw pointer to `std::vec::Vec` behind a reference, which is the object and not where it is kept at fixtures/reject/src/lib.rs:555:10 union `Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart at fixtures/reject/src/lib.rs:566:10 comparing pointers to `u32`, each a place JS makes of its own at fixtures/reject/src/lib.rs:574:2 @@ -228,6 +227,17 @@ a raw pointer to `(u32, u32)` in a variable assigned again while it may point th 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:2286:3 an assignment through a reference to `std::sync::LazyLock` at fixtures/reject/src/lib.rs:2291: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:2297:2 +a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as at fixtures/reject/src/lib.rs:2309:3 +a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2310:15 +a cast from a pointer to `u8` to one to `Halves`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2311:16 +a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2313:15 +unsupported call to `std::alloc::alloc_zeroed` at fixtures/reject/src/lib.rs:2338:13 +a cast from a pointer to `u8` to one to `u32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2338:12 +memory from `alloc` cast to pointers to `u32` and to `i32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2324:7 +memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2325:11 +memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2327:17 +memory from `alloc` cast to pointers to `u32` and to `i32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2328:21 +memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2330:17 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 @@ -243,7 +253,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:1300: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:1304: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:2086: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:2305: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:2305: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:2361: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:2305: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:2345: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:2345: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:2401: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:2345:23 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index 81f118fc2..4fb003b51 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -538,7 +538,7 @@ pub fn addressed(n: u32) -> usize { (&raw const n).addr() } -/// Memory from the allocator, which has no JS object to point at. +/// Memory from the allocator as bytes, which JS keeps only as values a cast types it as. pub fn allocated() -> u8 { let layout = std::alloc::Layout::new::(); unsafe { @@ -2298,6 +2298,46 @@ pub fn key_celled(key: &'static mut std::thread::LocalKey, next: std::threa key.with(|v| *v) } +/// Casts between pointers to bytes and to values outside memory from `alloc`, which read +/// the bytes of one type as another: of an array's bytes to `u32`s, of memory from +/// `alloc` to the crate's struct, which JS keeps as an object, and of a pointer to values +/// to one to their bytes, read rather than freed; and a byte of memory from `alloc` written +/// by `ptr::write`. +pub fn bytes_as_values(bytes: [u8; 4]) -> u32 { + let layout = std::alloc::Layout::new::(); + unsafe { + std::ptr::write(std::alloc::alloc(layout), 2); + let words = bytes.as_ptr() as *const u32; + let halves = std::alloc::alloc(layout) as *mut Halves; + let n = 7u32; + let byte = *((&raw const n) as *const u8); + *words + (*halves).low + u32::from(byte) + } +} + +/// Memory from `alloc` cast to pointers to two types, which reads one's bytes as the +/// other's, also once `realloc` grows it. +pub fn alloc_retyped() -> i32 { + let layout = std::alloc::Layout::new::(); + unsafe { + let raw = std::alloc::alloc(layout); + raw.cast::().write(u32::MAX); + let n = (raw as *mut i32).read(); + let grown = std::alloc::realloc(raw, layout, 8); + let m = grown.cast::().read(); + let words = grown.cast::(); + let again = std::alloc::realloc(words.cast::(), std::alloc::Layout::array::(2).unwrap(), 12); + let k = again.cast::().read(); + std::alloc::dealloc(again, std::alloc::Layout::array::(3).unwrap()); + n + m + k + } +} + +/// Memory from `alloc_zeroed`, whose zeroed bytes JS can't type. +pub fn zeroed() -> u32 { + unsafe { *(std::alloc::alloc_zeroed(std::alloc::Layout::new::()) as *mut u32) } +} + /// The tags of the first of two slots, each written whole as `fill` beside a pair of /// tags, then changed through a pointer to them. macro_rules! tagged_slots { diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 415f4ec3f..ce6c40ca7 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -181,6 +181,9 @@ roots = [ "lazy_celled", "lazy_assigned", "key_celled", + "bytes_as_values", + "zeroed", + "alloc_retyped", "cloned_slots", "dropped_slots", "ranked_slots", diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index dfb086ad9..f3f02c6a2 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -13,6 +13,7 @@ import { drop as dispose, dropAll, type Pending } from "./rt/task.ts"; import { AtIndex } from "./rt/collections.ts"; import { UNPRINTABLE } from "./rt/unicode.ts"; +export * from "./rt/alloc.ts"; export * from "./rt/ascii.ts"; export * from "./rt/bytes.ts"; export * from "./rt/collections.ts"; diff --git a/ts/runtime/rt/alloc.ts b/ts/runtime/rt/alloc.ts new file mode 100644 index 000000000..520258602 --- /dev/null +++ b/ts/runtime/rt/alloc.ts @@ -0,0 +1,175 @@ +// `std::alloc`, for rust2-ts output: a `Layout`, and memory from `alloc` that a cast of its +// pointer to one to `T` makes a JS array of `T`s, as many as fit. `rt.ts` re-exports all of it. +// +// JS keeps values, not bytes, so the memory is untyped until that cast, and a read or +// write of its bytes fails. `realloc` keeps the values in a new array, and `dealloc` lets +// the array go; a pointer into it after either reads what Rust leaves undefined. + +import { Err, type FmtResult, type Formatter, fmtDebugStructFields, Ok, panic, type Place, type Result } from "../rt.ts"; +import { AtIndex } from "./collections.ts"; +import type { usize } from "./int.ts"; + +/** `core::alloc::Layout`: a size, and an alignment that is a power of two. */ +export interface Layout { + readonly size: usize; + readonly align: usize; +} + +/** `core::alloc::LayoutError`, which holds nothing. */ +export class LayoutError {} + +/** The one `LayoutError`. */ +export const LAYOUT_ERROR = /* @__PURE__ */ new LayoutError(); + +/** The largest size a layout of `align` may have: wasm32's `isize::MAX`, less the padding up to `align`. */ +function maxSize(align: number): number { + return 0x7fffffff - (align - 1); +} + +/** `Layout::from_size_align`: an error unless `align` is a power of two and `size` rounded up to it fits in `isize`. */ +export function layoutFromSizeAlign(size: usize, align: usize): Result { + if (align === 0 || (align & (align - 1)) !== 0 || size > maxSize(align)) return Err(LAYOUT_ERROR); + return Ok({ size, align }); +} + +/** `Layout::array::(n)`, of `T`s of `size` bytes aligned to `align`. */ +export function layoutArray(n: usize, size: usize, align: usize): Result { + if (size !== 0 && n > Math.floor(maxSize(align) / size)) return Err(LAYOUT_ERROR); + return Ok({ size: size * n, align }); +} + +/** `Debug` of a `Layout`, as std's: its alignment also as a power of two. */ +export function fmtLayoutDebug(l: Layout, f: Formatter): FmtResult { + return fmtDebugStructFields( + f, + "Layout", + ["size", "align"], + [(f) => f.out.writeStr(String(l.size)), (f) => f.out.writeStr(`${l.align} (1 << ${31 - Math.clz32(l.align)})`)], + ); +} + +/** `Debug` of a `LayoutError`. */ +export function fmtLayoutErrorDebug(_: LayoutError, f: Formatter): FmtResult { + return f.out.writeStr("LayoutError"); +} + +/** `Display` of a `LayoutError`. */ +export function fmtLayoutErrorDisplay(_: LayoutError, f: Formatter): FmtResult { + return f.out.writeStr("invalid parameters to Layout::from_size_align"); +} + +/** + * Memory from `alloc`: `size` bytes, which hold the values of type `type`, `elem` bytes + * each, in `array` once a cast of a pointer to it types it. A pointer to its bytes is the block. + */ +export class Block implements Place { + readonly size: number; + array: unknown[] | undefined; + elem = 0; + type = ""; + + constructor(size: number) { + this.size = size; + } + + get v(): number { + return bytes(); + } + + set v(_: number) { + bytes(); + } +} + +function bytes(): never { + throw new Error("rust2-ts: a read or write of the bytes of memory from `alloc`, which JS keeps as values"); +} + +/** The block each array a cast of one's pointer made is in. */ +const BLOCKS = /* @__PURE__ */ new WeakMap(); + +/** `alloc::alloc`: `layout`'s bytes, untyped until a cast of the pointer to them. */ +export function alloc(layout: Layout): Block { + return new Block(layout.size); +} + +/** + * The pointer `p` to the bytes of a block, cast to one to the values of Rust type `type`, + * `elem` bytes each, it holds: the first of a new array of as many as fit, or of the array + * it holds already, of values of that type. + */ +export function allocAs(p: Place, elem: number, type: string): Place { + if (!(p instanceof Block)) { + throw new Error("rust2-ts: a pointer cast from one to bytes to one to values, which isn't memory from `alloc`"); + } + // As many as fit, past which bytes nothing can read are left over. + if (!p.array) { + p.array = new Array(Math.floor(p.size / elem)); + p.elem = elem; + p.type = type; + BLOCKS.set(p.array, p); + } + if (p.type !== type) throw new Error(`rust2-ts: memory from \`alloc\` holding \`${p.type}\`s, as \`${type}\`s`); + return new AtIndex(p.array as T[], 0); +} + +/** + * The pointer `p` to the first of the values of Rust type `type`, `elem` bytes each, that + * memory from `alloc`, or a `Vec`, holds, cast to one to its bytes: the block, which only + * `dealloc` and `realloc` take. + */ +export function allocBytes(p: Place, elem: number, type: string): Block { + if (!(p instanceof AtIndex) || p.index !== 0 || !Array.isArray(p.array)) { + throw new Error("rust2-ts: a pointer cast to one to bytes, which isn't to the start of memory from `alloc` or a `Vec`'s"); + } + const array = p.array as unknown[]; + let block = BLOCKS.get(array); + // A `Vec`'s, which `Layout::array` laid out as one block's is. + if (!block) { + block = new Block(array.length * elem); + block.array = array; + block.elem = elem; + block.type = type; + BLOCKS.set(array, block); + } + if (block.type !== type) throw new Error(`rust2-ts: memory from \`alloc\` holding \`${block.type}\`s, as \`${type}\`s`); + return block; +} + +/** The block the pointer `p` to bytes, which `dealloc` or `realloc` takes, is. */ +function blockOf(p: Place): Block { + if (!(p instanceof Block)) throw new Error("rust2-ts: memory freed or grown that isn't from `alloc`"); + return p; +} + +/** `alloc::dealloc` of the block `p`, whose values JS frees once nothing holds them. */ +export function dealloc(p: Place): void { + const block = blockOf(p); + if (block.array) BLOCKS.delete(block.array); + block.array = undefined; +} + +/** + * `alloc::realloc` of the block `p` to `size` bytes: a new block holding the values the old one + * did, as many as fit, and holes past them, which Rust leaves uninitialized. + */ +export function realloc(p: Place, size: usize): Block { + const block = blockOf(p); + const next = new Block(size); + const array = block.array; + if (array) { + const n = Math.floor(size / block.elem); + next.array = array.slice(0, n); + next.array.length = n; + next.elem = block.elem; + next.type = block.type; + BLOCKS.set(next.array, next); + dealloc(block); + } + return next; +} + +/** `alloc::handle_alloc_error`, which wasm32's std aborts by, as a panic does there. */ +export function handleAllocError(layout: Layout): never { + panic(`memory allocation of ${layout.size} bytes failed`); +} diff --git a/ts/src/census.rs b/ts/src/census.rs index 3064d2631..5d32ef700 100644 --- a/ts/src/census.rs +++ b/ts/src/census.rs @@ -167,6 +167,7 @@ const VERBATIM: &[&str] = &[ "Result", "Some", "UpperExp", + "alloc", "async fn", "become", "bool", diff --git a/ts/src/lower/alloc.rs b/ts/src/lower/alloc.rs new file mode 100644 index 000000000..e1076f366 --- /dev/null +++ b/ts/src/lower/alloc.rs @@ -0,0 +1,58 @@ +//! `std::alloc`, as `rt/alloc.ts` gives it: a `Layout` is its size and alignment, never +//! changed once made, and memory from `alloc` is untyped until a cast of its pointer to one +//! to `T`, which [`crate::lower::mir`] allows only where the body shows the pointer came +//! from `alloc` or `realloc`, makes it a JS array of `T`s. A pointer to it as bytes is +//! allowed only as what `dealloc` or `realloc` takes. + +use super::body::Body; +use super::library::Call; +use crate::ts::{Expr, Key}; + +/// A call of a `Layout` or allocator function, or its arguments back if it is none of theirs. +pub fn call<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + call: &Call<'tcx>, + owner: &str, + name: &str, + args: Vec, +) -> Result, Vec> { + let layout = |b: &mut Body<'_, '_, 'tcx>| { + let ty = call.args.types().next().expect("a type argument"); + super::iter::layout(b, ty, call.span) + }; + let mut args = args.into_iter(); + let mut arg = || args.next().expect("an argument"); + let e = match (owner, name) { + ("Layout", "new") => match layout(b) { + Ok((size, align)) => object(Expr::lit(size.to_string()), Expr::lit(align.to_string())), + Err(e) => e, + }, + ("Layout", "array") => match layout(b) { + Ok((size, align)) => Expr::rt( + "layoutArray", + vec![arg(), Expr::lit(size.to_string()), Expr::lit(align.to_string())], + ), + Err(e) => e, + }, + ("Layout", "from_size_align") => Expr::rt("layoutFromSizeAlign", vec![arg(), arg()]), + ("Layout", "from_size_align_unchecked") => object(arg(), arg()), + ("Layout", "size" | "align") => arg().member(name), + ("alloc", "alloc") => Expr::rt("alloc", vec![arg()]), + ("alloc", "dealloc") => Expr::rt("dealloc", vec![arg()]), + ("alloc", "realloc") => { + let (p, _, size) = (arg(), arg(), arg()); + Expr::rt("realloc", vec![p, size]) + } + ("alloc", "handle_alloc_error") => Expr::rt("handleAllocError", vec![arg()]), + _ => return Err(args.collect()), + }; + Ok(Some(e)) +} + +/// A `Layout` of `size` bytes aligned to `align`. +pub fn object(size: Expr, align: Expr) -> Expr { + Expr::Object(vec![ + (Key::Name("size".into()), size), + (Key::Name("align".into()), align), + ]) +} diff --git a/ts/src/lower/consts.rs b/ts/src/lower/consts.rs index ab79043b9..89eb8b7a3 100644 --- a/ts/src/lower/consts.rs +++ b/ts/src/lower/consts.rs @@ -105,6 +105,20 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { }, ty::Adt(adt, args) => { let fields = children(valtree); + // Its alignment is an enum, whose variant `i` is `1 << i`. + if Std::of(tcx, adt.did()) == Some(Std::Layout) { + let size = fields.first().and_then(|(_, size)| leaf(*size)); + let align = (fields.get(1).and_then(|(_, align)| children(*align).first().copied())) + .and_then(|(_, variant)| children(variant).first().copied()) + .and_then(|(_, index)| leaf(index)); + return match (size, align) { + (Some(size), Some(index)) => super::alloc::object( + Expr::lit(size.to_u32().to_string()), + Expr::lit((1u64 << index.to_u32()).to_string()), + ), + _ => self.err(span, format!("a constant of type `{ty}` without its fields")), + }; + } // Its nanoseconds are a pattern type, which only it reads. if Std::of(tcx, adt.did()) == Some(Std::Duration) { return match super::time::constant(&fields) { diff --git a/ts/src/lower/fmt.rs b/ts/src/lower/fmt.rs index 6c0a9bfec..dc7ddcda5 100644 --- a/ts/src/lower/fmt.rs +++ b/ts/src/lower/fmt.rs @@ -458,6 +458,9 @@ pub fn std_impl<'tcx>( (Std::Utf8Error, Trait::Debug) => rt("utf8ErrorDebug", vec![value, f]), (Std::Utf8Error, Trait::Display) => rt("utf8ErrorDisplay", vec![value, f]), (Std::Duration, Trait::Debug) => Expr::rt("durationDebug", vec![value, f]), + (Std::Layout, Trait::Debug) => rt("layoutDebug", vec![value, f]), + (Std::LayoutError, Trait::Debug) => rt("layoutErrorDebug", vec![value, f]), + (Std::LayoutError, Trait::Display) => rt("layoutErrorDisplay", vec![value, f]), (Std::Bytes, Trait::Debug) => Expr::rt("bytesDebug", vec![value, f]), (Std::BytesMut, Trait::Debug) => Expr::rt("bytesDebug", vec![Expr::rt("bytesMutView", vec![value]), f]), _ => return None, diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 1fa9c708b..769151019 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -169,6 +169,10 @@ pub enum Std { RandomState, /// `rt.Duration`: its seconds and nanoseconds, never changed once made. Duration, + /// `rt.Layout`: its size and alignment, never changed once made. + Layout, + /// `rt.LayoutError`. + LayoutError, /// `bytes::Bytes`: an `rt.ByteSlice`, as `&[u8]` is. Bytes, /// `bytes::BytesMut`: `rt.BytesMut`, a window of storage it shares with what it splits off. @@ -191,6 +195,7 @@ impl Std { | Self::ParseFloatError | Self::TryFromFloatSecsError | Self::FmtError + | Self::LayoutError ) } @@ -290,6 +295,8 @@ impl Std { .as_slice() { ["str", "error", "Utf8Error"] => Some(Self::Utf8Error), + ["alloc", "layout", "Layout"] => Some(Self::Layout), + ["alloc", "layout", "LayoutError"] => Some(Self::LayoutError), ["num", "error", "ParseIntError"] => Some(Self::ParseIntError), ["num", "error", "IntErrorKind"] | ["char", "convert", "CharErrorKind"] @@ -1340,6 +1347,10 @@ fn mapping<'tcx>( Ok(e) => return e, Err(args) => args, }; + let args = match super::alloc::call(b, call, &owner, &name, args) { + Ok(e) => return e, + Err(args) => args, + }; let args = match super::time::call(b, call, &owner, &name, args) { Ok(e) => return e, Err(args) => args, @@ -2390,12 +2401,17 @@ fn mapping<'tcx>( // Formatting. ("hint", "must_use") => args.pop()?, - // The size wasm32 lays the type out in, which translation targets. + // The size and alignment wasm32 lays the type out in, which translation targets. ("mem", "size_of") => { let env = b.t.typing_env_pub(); let layout = tcx.layout_of(env.as_query_input(targ(gargs, 0))).ok()?; Expr::lit(layout.size.bytes().to_string()) } + ("mem", "align_of") => { + let env = b.t.typing_env_pub(); + let layout = tcx.layout_of(env.as_query_input(targ(gargs, 0))).ok()?; + Expr::lit(layout.align.abi.bytes().to_string()) + } // What wasm32 lays out the value in: its type's, or a slice's elements' or a `str`'s // bytes'. A trait object's is its vtable's, which is left to the refusal of any other call. ("mem", "size_of_val" | "align_of_val") => { diff --git a/ts/src/lower/mir/alloc.rs b/ts/src/lower/mir/alloc.rs new file mode 100644 index 000000000..f501dd77f --- /dev/null +++ b/ts/src/lower/mir/alloc.rs @@ -0,0 +1,222 @@ +//! Casts of pointers to memory from `alloc`, as [`crate::lower::alloc`] models it: one from +//! `*mut u8` to `*mut T` the body shows came from `alloc` or `realloc` makes the memory a JS +//! array of `T`s, and one back to `*mut u8` is the block, only as what `dealloc` or +//! `realloc` takes. Any other cast between them reads the bytes of one type as another, +//! which stays refused. + +use rustc_middle::mir::{self, Local, Operand, ProjectionElem, Rvalue, TerminatorKind}; +use rustc_middle::ty::{self, Ty}; +use rustc_span::{Span, Spanned}; + +use super::Fx; +use crate::lower::library; +use crate::lower::pointer::Pointee; +use crate::ts::{Expr, Stmt}; + +fn byte(ty: Ty<'_>) -> bool { + matches!(ty.kind(), ty::Uint(ty::UintTy::U8)) +} + +/// Why the bytes of memory from `alloc` are refused. +pub(super) const BYTES: &str = + "a byte of memory from `alloc`, which JS keeps as the values a cast of its pointer types it as"; + +impl<'tcx> Fx<'_, '_, '_, 'tcx> { + /// The cast of the pointer `op` from one to `from` to one to `to`, if it is a cast of + /// memory from `alloc` between bytes and values JS copies. + pub(super) fn alloc_cast( + &mut self, + op: &Operand<'tcx>, + from: Ty<'tcx>, + to: Ty<'tcx>, + span: Span, + out: &mut Vec, + ) -> Option { + let (f, elem) = match (byte(from), byte(to)) { + (true, false) if self.typed(to) && self.allocated(op) => ("allocAs", to), + (false, true) if self.typed(from) && self.freed(op) => ("allocBytes", from), + _ => return None, + }; + let size = crate::lower::iter::size_of(self.b, elem, span); + // The type, not only its size: JS keeps `u32`s and `i32`s, for one, as the same numbers. + // A `MaybeUninit` is what it holds, as a cast between them keeps the pointer. + let key = self.alloc_key(elem); + // One body casting the memory to two types reads one's bytes as the other's; a + // pointer from elsewhere fails at run time. + if f == "allocAs" + && let Some(other) = self + .alloc_root(op) + .and_then(|root| self.alloc_types(root).find(|t| *t != key)) + { + return Some(self.err( + span, + format!( + "memory from `alloc` cast to pointers to `{key}` and to `{other}`, which reads the bytes of one type as another" + ), + )); + } + let p = self.operand(op, span, out); + Some(Expr::rt(f, vec![p, size, Expr::Str(key.to_string())])) + } + + /// Whether memory from `alloc` can be typed as `ty`s: values JS copies, of some bytes. + fn typed(&self, ty: Ty<'tcx>) -> bool { + !byte(ty) + && !self.b.t.zero_sized(ty) + && self.b.t.pointee(ty) == Pointee::Place + && ty.is_sized(self.tcx, self.b.t.typing_env_pub()) + } + + /// The type a cast to pointers to `ty` types memory from `alloc` as. + fn alloc_key(&self, ty: Ty<'tcx>) -> Ty<'tcx> { + self.b.t.erased(self.b.t.uninit_inner(ty)) + } + + /// The types each cast in the body of a pointer to the memory `alloc` returned into + /// `root` types it as. + fn alloc_types(&self, root: Local) -> impl Iterator> + '_ { + let (decls, tcx) = (&self.mir.local_decls, self.tcx); + let casts = self.mir.basic_blocks.iter().flat_map(move |data| { + let assigns = data.statements.iter().filter_map(move |stmt| match &stmt.kind { + mir::StatementKind::Assign(assign) => match &assign.1 { + Rvalue::Cast(mir::CastKind::PtrToPtr, op, to) => { + Some((op, op.ty(decls, tcx).builtin_deref(true)?, to.builtin_deref(true)?)) + } + _ => None, + }, + _ => None, + }); + let call = || { + let TerminatorKind::Call { func, args, .. } = &data.terminator().kind else { + return None; + }; + let ty::FnDef(def_id, gargs) = *func.ty(decls, tcx).kind() else { + return None; + }; + let (owner, name) = library::key(tcx, def_id)?; + if !library::is_std(tcx, def_id) || !matches!(owner.as_str(), "ptr" | "NonNull") || name != "cast" { + return None; + } + let mut types = gargs.skip_binder().types(); + Some((&args.first()?.node, types.next()?, types.next()?)) + }; + assigns.chain(call()) + }); + casts + .filter(move |&(op, from, to)| byte(from) && self.typed(to) && self.alloc_root(op) == Some(root)) + .map(|(_, _, to)| self.alloc_key(to)) + } + + /// A call of `cast` on a pointer, if it is a cast of memory from `alloc`, as [`Self::alloc_cast`] has it. + pub(super) fn alloc_cast_call( + &mut self, + def_id: rustc_hir::def_id::DefId, + gargs: ty::GenericArgsRef<'tcx>, + args: &[Spanned>], + span: Span, + out: &mut Vec, + ) -> Option { + let (owner, name) = library::key(self.tcx, def_id)?; + if !library::is_std(self.tcx, def_id) || !matches!(owner.as_str(), "ptr" | "NonNull") || name != "cast" { + return None; + } + let mut types = gargs.types(); + let (from, to) = (types.next()?, types.next()?); + self.alloc_cast(&args.first()?.node, from, to, span, out) + } + + /// Why `place` can't be read or written, if it is a byte of memory from `alloc`, as the + /// body shows, which JS keeps as values once a cast types it, and never as bytes. + pub(super) fn alloc_bytes(&self, place: mir::Place<'tcx>) -> Option<&'static str> { + let [ProjectionElem::Deref, ..] = place.projection.as_slice() else { + return None; + }; + let ty::RawPtr(pointee, _) = self.mir.local_decls[place.local].ty.kind() else { + return None; + }; + (matches!(pointee.kind(), ty::Uint(ty::UintTy::U8)) + && self.allocated(&Operand::Copy(mir::Place::from(place.local)))) + .then_some(BYTES) + } + + /// Whether the pointer `op` is one `alloc` or `realloc` returned, as the body shows. + pub(super) fn allocated(&self, op: &Operand<'tcx>) -> bool { + self.alloc_root(op).is_some() + } + + /// The local `alloc` returned the pointer `op` into, as the body shows, or `realloc` + /// did, which keeps the type of the memory it grows when that is in the body too. + fn alloc_root(&self, op: &Operand<'tcx>) -> Option { + let local = self.traced(op)?; + if self.def_at[local].is_some() { + return match self.def_rvalue(local) { + Some(Rvalue::Use(op, _) | Rvalue::Cast(mir::CastKind::PtrToPtr, op, _)) => match op.place() { + // A payload out of an `Option`, a `Result`, or `?`'s `ControlFlow`. + Some(place) + if let [ProjectionElem::Downcast(..), ProjectionElem::Field(..)] = + place.projection.as_slice() => + { + self.alloc_root(&Operand::Copy(mir::Place::from(place.local))) + } + _ => self.alloc_root(op), + }, + _ => 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()) { + ("alloc", "alloc") => library::is_std(self.tcx, def_id).then_some(local), + ("alloc", "realloc") if library::is_std(self.tcx, def_id) => self.alloc_root(&args[0].node).or(Some(local)), + ("NonNull", "new" | "new_unchecked" | "as_ptr") + | ("ptr" | "NonNull", "cast" | "cast_const" | "cast_mut") + | ("Option", "ok_or" | "ok_or_else" | "unwrap" | "expect" | "unwrap_unchecked") + | ("Result", "unwrap" | "expect" | "unwrap_unchecked") + | ("Try", "branch") => self.alloc_root(&args[0].node), + _ => None, + } + } + + /// Whether the cast of the pointer `op` gives only what `dealloc` or `realloc` takes. + fn freed(&self, op: &Operand<'tcx>) -> bool { + self.mir.basic_blocks.iter().any(|data| { + let TerminatorKind::Call { func, args, .. } = &data.terminator().kind else { + return false; + }; + let ty::FnDef(def_id, _) = *func.ty(&self.mir.local_decls, self.tcx).kind() else { + return false; + }; + let freeing = library::is_std(self.tcx, def_id) + && matches!( + library::key(self.tcx, def_id), + Some((owner, name)) if owner == "alloc" && matches!(name.as_str(), "dealloc" | "realloc") + ); + freeing && args.first().is_some_and(|arg| self.cast_of(&arg.node, op)) + }) + } + + /// Whether `arg` is read only here, and is the cast of `op` alone, by `as` or `cast`, + /// and a `NonNull`'s through `as_ptr`. + fn cast_of(&self, arg: &Operand<'tcx>, op: &Operand<'tcx>) -> bool { + let Some(local): Option = arg.place().and_then(|p| p.as_local()) else { + return false; + }; + if self.uses[local] != 1 || self.traced(arg).is_none() { + return false; + } + if self.def_at[local].is_some() { + return matches!(self.def_rvalue(local), Some(Rvalue::Cast(mir::CastKind::PtrToPtr, cast, _)) if std::ptr::eq(cast, op)); + } + let Some((def_id, _, args)) = self.def_call(local) else { + return false; + }; + match library::key(self.tcx, def_id) { + Some((owner, name)) => match (owner.as_str(), name.as_str()) { + ("ptr" | "NonNull", "cast") => std::ptr::eq(&args[0].node, op), + ("NonNull", "as_ptr") => self.cast_of(&args[0].node, op), + _ => false, + }, + None => false, + } + } +} diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index a887892cf..02d351d92 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -105,6 +105,11 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { 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) + if let Some(cast) = self.alloc_cast_call(def_id, gargs.skip_binder(), args, span, &mut pre) => + { + (cast, 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 14da47c00..4407424fa 100644 --- a/ts/src/lower/mir/mod.rs +++ b/ts/src/lower/mir/mod.rs @@ -10,6 +10,7 @@ //! Library calls go through the mappings in `library`, which take their arguments //! lowered: a `Body` holds the names, and its translator everything else. +mod alloc; mod call; mod cfg; pub mod coroutine; diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 0896b6618..c22d6ea0a 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -72,6 +72,14 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { if self.b.t.zero_sized(pointee) || self.b.t.pointee(pointee) != Pointee::Place { return None; } + // Only a cast types memory from `alloc`, which has no bytes before or after. + if matches!(pointee.kind(), ty::Uint(ty::UintTy::U8)) + && matches!(owner.as_str(), "ptr" | "NonNull" | "raw") + && !matches!(name.as_str(), "cast" | "as_ptr" | "new" | "new_unchecked" | "is_null") + && args.iter().any(|a| self.allocated(&a.node)) + { + return Some(super::alloc::BYTES.into()); + } 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); @@ -311,7 +319,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } /// The local `op` reads whole, if the body assigns it once. - fn traced(&self, op: &Operand<'tcx>) -> Option { + pub(super) fn traced(&self, op: &Operand<'tcx>) -> Option { let local = op.place()?.as_local()?; (self.defs[local] == 1 && !self.is_arg(local)).then_some(local) } @@ -325,7 +333,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { /// The function, its generic arguments, and its arguments, of the call of std's that /// assigns `local`. - fn def_call( + pub(super) fn def_call( &self, local: Local, ) -> Option<( diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 1f0a6fe95..30afde223 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -62,6 +62,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { /// The JS expression of `place`, read without a copy. pub fn place_expr(&mut self, place: Place<'tcx>, span: Span, out: &mut Vec) -> Expr { let tcx = self.tcx; + if let Some(why) = self.alloc_bytes(place) { + return self.err(span, why); + } // A local held as its parts reads them, as far as they go. if self.scalars.contains_key(&place.local) { let (e, n) = self.scalar_place(place, span); @@ -720,6 +723,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { pub(super) fn store(&mut self, place: Place<'tcx>, value: Expr, span: Span) -> Vec { let (place, value) = self.rebuilt(place, value, span); let mut out = Vec::new(); + if let Some(why) = self.alloc_bytes(place) { + out.push(StmtKind::Expr(self.err(span, why)).at(None)); + return out; + } // A raw pointer to a value of no bytes may dangle, and a write through it writes nothing. if let [.., ProjectionElem::Deref] = place.projection.as_slice() && matches!(self.mir.local_decls[place.local].ty.kind(), ty::RawPtr(..)) diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index e30e73933..bf8a612be 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -401,6 +401,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { if let (Some(a), Some(b)) = (from.builtin_deref(true), to.builtin_deref(true)) && from.is_raw_ptr() => { + if let Some(cast) = self.alloc_cast(op, a, b, span, out) { + return cast; + } let p = self.operand(op, span, out); crate::lower::pointer::cast(self.b, p, a, b, span) } @@ -711,6 +714,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Expr::New(Box::new(Expr::Rt("RangeFrom".into())), values) } (Std::RangeFull, _) => Expr::undefined(), + (Std::LayoutError, _) => Expr::Rt("LAYOUT_ERROR".into()), (Std::ParseIntError, _) => Expr::Object(vec![(Key::Name("kind".into()), values.pop().unwrap())]), (Std::ErrorKind, _) => Expr::As(Box::new(Expr::Str(def.name.to_string())), self.b.t.ty(ty, span)), (Std::Ordering, _) => crate::lower::collections::ordering_variant(variant), diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 76a37e88e..e175f6f2d 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -5,6 +5,7 @@ //! call is always a direct call. Types are translated once, generically, and a function of //! a type is a module-level one taking `self` first, so a bundler drops what goes unused. +mod alloc; mod bind; pub use bind::check_module; mod body; @@ -1745,6 +1746,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { library::Std::Hasher => Type::Rt("Hasher", vec![]), library::Std::RandomState => Type::Rt("RandomState", vec![]), library::Std::Duration => Type::Rt("Duration", vec![]), + library::Std::Layout => Type::Rt("Layout", vec![]), + library::Std::LayoutError => Type::Rt("LayoutError", vec![]), library::Std::Bytes => Type::Rt("ByteSlice", vec![]), library::Std::BytesMut => Type::Rt("BytesMut", vec![]), library::Std::ThreadRng => Type::Rt("ThreadRng", vec![]), diff --git a/ts/src/program.rs b/ts/src/program.rs index 4cea9e47f..c507b0c0a 100644 --- a/ts/src/program.rs +++ b/ts/src/program.rs @@ -15,6 +15,7 @@ use crate::ts::{self, FieldId, Function, Item, SymId}; /// file's path from there, and its text. pub const RUNTIME: &[(&str, &str)] = &[ ("rt.ts", include_str!("../runtime/rt.ts")), + ("rt/alloc.ts", include_str!("../runtime/rt/alloc.ts")), ("rt/ascii.ts", include_str!("../runtime/rt/ascii.ts")), ("rt/bytes.ts", include_str!("../runtime/rt/bytes.ts")), ("rt/collections.ts", include_str!("../runtime/rt/collections.ts")), diff --git a/ts/tests/pointers.ts b/ts/tests/pointers.ts index 6295114e7..82fa7e3fd 100644 --- a/ts/tests/pointers.ts +++ b/ts/tests/pointers.ts @@ -54,6 +54,24 @@ unsure(() => { }); assert.equal(vec.length, 1); +// Memory from `alloc`, which JS keeps as values once a cast types it: its bytes, typed as +// another type of the same size, a pointer past the start of what it holds cast back to +// bytes, and one to its values passed as if a block. +const block = rt.alloc({ size: 12, align: 4 }); +unsure(() => block.v); +const words = rt.allocAs(block, 4, "u32"); +words.v = 0xffffffff; +assert.equal(rt.allocAs(block, 4, "u32").v, 0xffffffff); +unsure(() => rt.allocAs(block, 4, "i32")); +unsure(() => rt.allocBytes(words, 4, "f32")); +unsure(() => rt.allocBytes(rt.ptrAdd(words, 1), 4, "u32")); +// Grown, it keeps its values, as many as fit; freed or grown, a pointer to its values is +// no block. +unsure(() => rt.dealloc(words)); +unsure(() => rt.realloc(words, 16)); +const grown = rt.realloc(rt.allocBytes(words, 4, "u32"), 18); +assert.deepEqual(Array.from(grown.array ?? []), [0xffffffff, undefined, undefined, undefined]); + // A dangling pointer read through, written through, or copied from, which Rust leaves // undefined, rather than reading `undefined`. const dangling: rt.Place = rt.DANGLING;