Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ generic std gap is planned here even when moq never calls it.
- [The rest of a vector's methods](/quest/m3/ts-vec-rest.md) - `try_reserve`, a deque's rotations and searches, and `Vec::splice` translate
- [A reversed zip's per-pair cost](/quest/m3/ts-zip-rev-overhead.md) - a reversed by-index zip costs per pair what a forward one does, or the gap is explained
- [Type errors at the Rust that made them](/quest/m3/ts-span-precision.md) - a `tsc` error on output names the tail expression or call argument that made it
- [Fresh writes through a zip, a copy, and a repeat](/quest/m3/ts-fresh-shapes.md) - zipping a `Vec<[T; N]>`, reading a `T` out of `Zip::next`, and `copied` or `vec![x; n]` over a `Copy` type translate
- [Fresh writes beside a zipped iterator that points at a part](/quest/m3/ts-fresh-shapes.md) - zipping slots with a `Vec<[T; N]>`, `vec![row; n]`, or `copied` of what the slot holds translates
- [Fresh writes through a vector's own accessors](/quest/m3/ts-fresh-vec-access.md) - a whole write into a vector's element stays fresh through a shared borrow, `first_mut`, a by-value array `for`, and a byte-holding collect
- [Python's wake on the loop's own thread](/quest/m3/ffi-python-wake.md) - a wake on the event loop's thread resumes the future with no socket pair
- [Python's call cost](/quest/m3/ffi-python-calls.md) - a call on an object costs what BoltFFI's does, or the gap is explained
- [A std map with a crate's own BuildHasher](/quest/m3/ts-map-own-hasher.md) - std's and hashbrown's maps built with a crate's own hasher translate for keys that are JS objects
46 changes: 28 additions & 18 deletions quest/m3/ts-fresh-shapes.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
# [M] Fresh writes through a zip, a copy, and a repeat
# [M] Fresh writes beside a zipped iterator that points at a part

## Goal

[rust2-ts](/ts/README.md) translates three shapes it refuses for concrete types and type
[rust2-ts](/ts/README.md) translates a whole write into a slot zipped with an iterator
whose pointers point at a type the slot holds in place, for concrete types and type
parameters alike, proven by differential tests against native Rust:

- zipping a slot with a `Vec<[T; N]>`, as `[u8; N]`;
- reading a `T` back out of `Zip::next`'s result;
- `iter().copied()` and `vec![x; n]` over a `Copy` type, a crate's own or a `T: Copy`.
- `slots.iter_mut().zip(rows)` of a `Vec<[T; N]>` or `vec![row; n]`, writing `[T; N]`
rows into `([T; N], (u8, u8))` slots, as `[u8; N]` too;
- `slots.iter_mut().zip(fill.iter().copied())` where the slot holds `fill`'s element type.

## Plan

Found by [#394](https://github.com/moq-dev/rust2/pull/394), which made a whole write into
fresh memory count as fresh in a function generic over a type. These three stay refused for
reasons that have nothing to do with type parameters:
Found by [#516](https://github.com/moq-dev/rust2/pull/516), which made a value of a type
parameter read out of `Zip::next`'s result, `copied`, and `vec![x; n]` judged fresh. The
one cause left: `Trace::fresh`'s check of std-made pointers reads a live `Zip`'s whole
type, so the `IntoIter<[T; N]>` or `Iter<T>` half, which no pointer into the slot made,
counts as pointing at the slot's `[T; N]` part. `origins` also adds each std call in the
chain, the `zip` itself among them, as memory it may have allocated.

- `vec::IntoIter` points at a type the slot holds in place, so zipping with it refuses.
- Reading a `T` out of `Zip::next`'s result makes it a holder. A concrete control
(`Vec<Option<u8>>`) is refused as well.
- `Copy`'s supertrait `Clone` selects a parameter candidate, so `copied` and `vec![x; n]`
count as running the crate's `Clone`. The two differ: `copied` copies and calls no
`clone`, while `vec![x; n]` calls `clone` `n - 1` times, and a `Copy` type's hand-written
`Clone` may have effects or panic. Treat `copied` as running no crate code; keep
`vec![x; n]` calling the crate's `clone`, judged fresh only where that `clone` writes
nothing the slot holds. Cover both with differential tests whose `Clone` logs and panics.
Neither the type of a pointer nor what a type holds in place tells the halves apart:
std's code can make a pointer to a tuple's field, as `RangeBounds` for
`(Bound<T>, Bound<T>)` does. Options:

Reproduce each first and fix the one that is a single cause before the others.
- Std's code generic over a type parameter keeps what it was given under that parameter:
match a std call's generic signature against which arguments hold pointers, and mask the
result's parts under parameters none of them bind. In-place `collect` and other
specializations relate parameters through bounds, so a bound naming both must taint.
- A table of std's iterator adaptors and what each part of their result is made of, as
`pointer::derives` is for pointer functions.

Recommend the first if it stays sound under specialization; it covers every adaptor at
once.

Public API: the translator's output.

## Related

- [Fresh writes through a vector's own accessors](/quest/m3/ts-fresh-vec-access.md) - the other refusals found beside this one
32 changes: 32 additions & 0 deletions quest/m3/ts-fresh-vec-access.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# [M] Fresh writes through a vector's own accessors

## Goal

[rust2-ts](/ts/README.md) translates a whole write into a vector's element, then a raw
pointer into a part of it, where the vector is reached through std's own accessors, proven
by differential tests against native Rust. Each is refused today with "a raw pointer to
`(u8, u8)` in ..., which the crate may put another object in place of", for concrete types
too:

- a shared borrow of the vector, or of the one it was collected from, such as `v.len()` or
`other.iter().copied().collect()`: `Trace::origins` gives up on a local any shared borrow
takes, as if one could change it;
- `v.first_mut()` and `v.get_mut(i)`: a std call given a `&mut [T]` counts as replacing
`T`'s parts, as a slice is no object `fills_fresh` judges;
- a `for` over an array by value: `array::IntoIter::next` is given the `&mut` to an
iterator holding the elements in place, which counts as replacing them;
- a vector collected from a std iterator, of a type that holds a byte in place: a `Vec`'s
buffer pointer is `Unique<u8>`, so it reads as pointing at that byte.

## Plan

Found by [#516](https://github.com/moq-dev/rust2/pull/516), whose tests step around each.
They are separate causes; reproduce each and fix the one that is a single cause first. A
`Vec`'s buffer typed by its element would make `points_to` exact, but a heap buffer is no
part of another value, so check it refuses nothing new.

Public API: the translator's output.

## Related

- [Fresh writes beside a zipped iterator that points at a part](/quest/m3/ts-fresh-shapes.md) - the zip half of #516's leftovers
15 changes: 15 additions & 0 deletions ts/fixtures/lang/lengths/caller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// The inputs `lengths`' cases name, which run with the generated ones.

import { op } from "./lang.ts";

export { ops, run } from "./lang.ts";

/**
* `lengths::lengths` at each length, with `0xff` among the rows `copied` copies, which
* clones none, and as the value each `vec![x; n]` clones, which panics, in a body generic
* over a type and in one over none.
*/
const lengthsOp = op("lengths::lengths");
export const cases: Uint8Array[] = [0, 1, 2, 3].flatMap((kind) =>
[[0xff, 0, 0, 0xff], [0, 0xff, 0], [0, 0, 0xff]].map((data) => Uint8Array.of(...lengthsOp, kind, ...data)),
);
29 changes: 29 additions & 0 deletions ts/fixtures/lang/lengths/expected/lengths.Loud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Generated by rust2-ts. Do not edit.

import * as rt from "./rt.ts";
import { CLONES } from "./lengths.ts";

/**
* A `Copy` type whose `Clone`, written by hand, logs its value, panics on `0xff`, and
* counts up, so a clone tells from a copy: `copied` never calls it, and `vec![x; n]` calls
* it `n - 1` times.
*/
export interface Loud {
_0: rt.u16;
}

export function clone$Clone(self: Loud): Loud {
rt.localKeyWithBorrowMut(CLONES, (log: rt.ByteVec): undefined => {
rt.iterExtendBytes(log, rt.iterSlice(rt.u16ToBeBytes(self._0).reverse()), undefined, true);
});
if (!(self._0 === 255)) {
return { _0: rt.u16WrappingAdd(self._0, 1) };
}
rt.assertFailed("!=", (f: rt.Formatter) => rt.u16Debug(self._0, f), (f: rt.Formatter) => rt.u16Debug(255, f), ["a clone of 0xff"]);
}

export function put$lengths_Put(self: Loud, out: rt.ByteVec): void {
rt.iterExtendBytes(out, rt.iterSlice(rt.u16ToBeBytes(self._0).reverse()), undefined, true);
}

//# sourceMappingURL=lengths.Loud.ts.map
22 changes: 22 additions & 0 deletions ts/fixtures/lang/lengths/expected/lengths.Row.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Generated by rust2-ts. Do not edit.

import * as rt from "./rt.ts";
import { copy$tuple2_u16_u16 } from "./lib.ts";
import * as Loud from "./lengths.Loud.ts";

export function clone$Clone_lengths_Loud(self: Row<Loud.Loud>): Row<Loud.Loud> {
return new Row(Loud.clone$Clone(self._0), copy$tuple2_u16_u16(self._1));
}

/** A value and a pair of tags kept as an object, of no byte held in place. */
export class Row<T> {
_0: T;
_1: [rt.u16, rt.u16];

constructor(_0: T, _1: [rt.u16, rt.u16]) {
this._0 = _0;
this._1 = _1;
}
}

//# sourceMappingURL=lengths.Row.ts.map
Loading