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
1 change: 0 additions & 1 deletion quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ generic std gap is planned here even when moq never calls it.
- [A raw pointer's staleness bound by its liveness](/quest/m3/ts-pointer-liveness.md) - a pointer dead before its variable is assigned again, as in an earlier loop iteration, translates
- [Writes into fresh memory before any pointer to it](/quest/m3/ts-fresh-writes.md) - a whole write filling a fresh allocation or `MaybeUninit` before any pointer into it no longer refuses later pointers, as kio's `Segment::new` slots
- [Unsafe code the translator can model](/quest/m3/ts-unsafe.md) - a dependency using only `unsafe` the translator can model translates from source
- [f32](/quest/m3/ts-f32.md) - `f32` and `Duration`'s `f32` methods translate as `f64` does
- [The capacity of a BytesMut](/quest/m3/ts-bytes-capacity.md) - `BytesMut::capacity` and what reads it translate with `bytes`' values
- [BufMut's provided writes through a &mut](/quest/m3/ts-buf-mut-forward.md) - `put`, `put_bytes`, `put_uint`, and `put_int` of a `&mut Vec<u8>` or `&mut BytesMut` translate
- [Trait objects](/quest/m3/ts-dyn.md) - `dyn Error`, `dyn Iterator`, `dyn Buf`, and `Arc<dyn Debug>` translate, so a consumer neither rewrites nor binds them
Expand Down
21 changes: 0 additions & 21 deletions quest/m3/ts-f32.md

This file was deleted.

3 changes: 1 addition & 2 deletions quest/m3/ts-unsafe.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ none of its own `unsafe`.
([#222](https://github.com/moq-dev/rust2/pull/222)): the bytes keep their type, so
this reinterprets nothing. Those getters also need `<[u8]>::get` and `get_mut` of a
range, and each integer's `from_le_bytes` and `from_ne_bytes`, and `chunks_vectored`
needs `std::io::IoSlice`: 184 of moq-net's 196 refusals in `bytes`, the rest being 12
of [`f32`](/quest/m3/ts-f32.md).
needs `std::io::IoSlice`: all 184 of moq-net's refusals in `bytes`.
- `BinaryHeap` and `PeekMut` translate from std's MIR, whose `Hole` moves elements
through raw pointers into the `Vec`'s storage, replacing `rt.BinaryHeap` and
`rt.PeekMut`, the hand-written port of std's sifts that
Expand Down
28 changes: 22 additions & 6 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,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`, collections in `rt/collections.ts`, hashing in `rt/hash.ts`, ownership, atomics, and async in
`rt/task.ts`, `f64`'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`, and `Duration` in `rt/time.ts`. What translates from std's own MIR,
`Option`'s, `Result`'s, and `Poll`'s methods, `==` of a `Result` and of std's errors, `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
Expand All @@ -82,6 +82,7 @@ crate translation reaches is refused, and so are two versions of one crate it re
| `u8`-`u32`, `i8`-`i32`, `usize`, `isize` | `rt.u8` and so on: a `number` branded with its type, checked: overflow panics |
| `u64`, `i64`, `u128`, `i128` | `rt.u64` and so on: a branded `number` while a safe integer, 32-bit limbs past 2^53, checked the same way |
| `f64` | `rt.f64`: a `number` branded so, which is the same IEEE 754 double |
| `f32` | `rt.f32`: a `number` branded so, which a single holds exactly |
| `struct` | a plain object, typed by an interface; a class if it may drop |
| `enum` without data | a union of strings: `"Lite01" \| "Lite02"`, or `never` without variants |
| `enum` | a union of objects tagged by variant |
Expand Down Expand Up @@ -167,18 +168,33 @@ NaN an operation makes, so a NaN's payload and sign are the engine's: Node and C
keep them through `from_bits` and `to_bits`, but V8 quiets a signaling NaN stored in an
array, as a `Vec<f64>` holds it.

An `f32` is a JS number a single holds exactly. Each arithmetic result, and each of
`sqrt`, `recip`, and libm's functions, rounds to a single with `Math.fround`, which gives
the single nearest the exact value, since a double holds the exact result closely enough;
`%`, comparisons, and negation need no rounding. Its methods compute in singles where std
does, as `div_euclid`, `powi`, and `atanh` do, and in doubles where std does, as wasm32's
`midpoint` does. A cast to `f64` is the same number, and `f64 as f32` is `Math.fround`;
an integer rounds to a single once, never through the nearest double, which could round
twice. A NaN keeps its sign and payload at the top of a double NaN's, so `from_bits` and
`to_bits` keep a signaling one, which JS's conversion from a single would quiet, and V8
quiets one stored in an array as it does an `f64`'s.
`Duration`'s `f32` methods are std's: `as_secs_f32` and `div_duration_f32` round each step
to a single, and the rest go through `f64` as std's do.

`format!`, `write!`, `panic!`, `assert!`, and the other macros of `format_args!`
translate, as do `Display` and `Debug` impls, derived and thiserror's included. The
runtime ports `core::fmt`, so the text matches native Rust's byte for byte. A placeholder
becomes a function writing its value, and `fmt::Arguments` an array of literal pieces
and such functions: `rt.fmtWriteFmt(f, ["code=", (f) => rt.u16Display(code, f)])`. A `&dyn
Debug` or `&dyn Display` is such a function too. Format specs take every option but `{:p}`:
fill, alignment, `+`, `#`, `0`, `x?` and `X?`, a width and a precision, each also from an
argument, and `x`, `X`, `o`, `b`, `e`, and `E` for integers, `e` and `E` for `f64`. An `f64`
argument, and `x`, `X`, `o`, `b`, `e`, and `E` for integers, `e` and `E` for floats. An `f64`
prints std's digits: the shortest that read back as it, the greater of two as close where
JS's would take the even one, or its exact value rounded half to even to a precision,
where `toFixed` rounds half up. `str::parse::<f64>` takes std's grammar, not `Number`'s,
and rounds to the nearest double. The `str::parse` and `from_str_radix` of an integer or a
where `toFixed` rounds half up. An `f32` prints the shortest digits that read back as the
single, found as Dragon does, which JS has no function for. `str::parse` of a float takes
std's grammar, not `Number`'s, and rounds to the nearest of the type once, so an `f32`
never rounds through a double. The `str::parse` and `from_str_radix` of an integer or a
`NonZero` are core's own, over the string's UTF-8 bytes; their `ParseIntError` is
`rt.ParseIntError`, `{ kind }`, whose `IntErrorKind` is the variant's name. `bool`'s is
core's too, failing with an `rt.ParseBoolError`; a `char`'s is `rt.charFromStr`, failing with
Expand Down Expand Up @@ -381,7 +397,7 @@ of them:
`split_off`, `try_reclaim`, `try_unsplit`, and `spare_capacity_mut`, which read it.
- `Buf` and `BufMut` of a `Bytes`, a `BytesMut`, a `&[u8]`, and a `Vec<u8>` map: the
required methods, `has_remaining`, `copy_to_slice`, `copy_to_bytes`, each `get_` and
`put_` of an integer or `f64`, `put_slice`, `put_bytes`, and `put`, whose panics are
`put_` of an integer or a float, `put_slice`, `put_bytes`, and `put`, whose panics are
`bytes`'. `put` of the crate's own `Buf` into a `Vec<u8>` reads it through its methods
as `bytes` does, and into a `BytesMut` is refused, as `bytes` picks those methods by its
capacity. The provided `copy_to_bytes` of the crate's own `Buf`, whose `BytesMut` has
Expand Down Expand Up @@ -611,7 +627,7 @@ fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait
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
hand-written TS ([bindings](#bindings)).

These fail translation: `f32` ([`f32`](/quest/m3/ts-f32.md)), async closures, a `Poll` of a `Poll` of a `Poll`, a future whose output can be pending, an instance of a generic type whose field is a `Poll` of a type parameter that can be pending, a closure that owns a value
These fail translation: `f16` and `f128`, async closures, a `Poll` of a `Poll` of a `Poll`, a future whose output can be pending, an instance of a generic type whose field is a `Poll` of a type parameter that can be pending, a closure that owns a value
that drops given to a std function that drops it beside another value that drops, as
`for_each` of items that drop does, or keeps it other than in an iterator adaptor, and
values that drop in some iterators ([drop order](/quest/m3/ts-drop-order.md)), a `Cow` of other than `str`, a std
Expand Down
79 changes: 72 additions & 7 deletions ts/fixtures/lang/caller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,81 @@ const parsed = [
`1${"0".repeat(800)}e-800`, `${"9".repeat(800)}e-1100`, "1e65535", `1e-65536`, `0.${"0".repeat(70000)}1e70000`,
].map((text) => Uint8Array.of(op("floats::parses"), 0, ...new TextEncoder().encode(text)));

/** How many ULPs from native Rust's each `f64` of `floats`' kind 7 may be, which Rust leaves to libm. */
/** A single's bits, big-endian, as `singles` takes them. */
function single(x: number | bigint): number[] {
const bytes = new Uint8Array(4);
const view = new DataView(bytes.buffer);
if (typeof x === "bigint") view.setUint32(0, Number(x));
else view.setFloat32(0, x);
return [...bytes];
}

/**
* `singles`' operands: each side of a half, of 2^23 and 2^24, where each integer type
* saturates, where a single's range and its normals end, the bounds of `Debug`'s
* exponent, infinities, and NaNs, one negative, one with a payload, and one signaling.
*/
const singlesValues: (number | bigint)[] = [
0, -0, 0.5, -0.5, 1.5, 2.5, -2.5, 0.49999997, 1, -1, 3, 0.1, 1 / 3, 2 ** 23 - 0.5, 2 ** 23, 2 ** 24, 2 ** 24 + 2, 255.5, -128.5,
2147483520, 2 ** 31, -(2 ** 31), 4294967040, 2 ** 32, 2 ** 63, -(2 ** 63), 2 ** 64, 2 ** 127, 1e-9, 0.99999994, 1.0000001, 1e-4, 1e16,
1e-40, 2 ** -149, 2 ** -126, 3.4028234663852886e38, -3.4028234663852886e38, Infinity, -Infinity, NaN, 0xffc00000n, 0x7fc00123n, 0x7f800001n,
];
const singlesOp = op("singles::singles");
const singlesCases = [
...Array.from({ length: 10 }, (_, kind) => singlesValues.flatMap((a) => singlesValues.map((b) => Uint8Array.of(singlesOp, kind, ...single(a), ...single(b), 1)))).flat(),
// `from_secs_f32`, `mul_f32`, and `div_f32` panicking, and `Duration::MAX.mul_f32`.
...[18, 28, 38].flatMap((kind) => singlesValues.map((a) => Uint8Array.of(singlesOp, kind, ...single(a), ...single(a), 3))),
// Integers whose `f32` rounds once where through an `f64` it would round twice: past
// 2^53, a bit just past halfway between two singles.
...wides.concat([(1n << 60n) + (1n << 36n) + 1n, (1n << 64n) - (1n << 39n) - 1n, (1n << 127n) + (1n << 103n) + 1n, (1n << 24n) + 1n]).map((v) => {
const bytes = new Uint8Array(16);
new DataView(bytes.buffer).setBigUint64(0, BigInt.asUintN(64, v >> 64n));
new DataView(bytes.buffer).setBigUint64(8, BigInt.asUintN(64, v));
return Uint8Array.of(singlesOp, 2, ...bytes);
}),
];

/** `singles::formats`' singles: ties at each precision, carries through nines, and the shortest digits at each end. */
const singlesFormatted: (number | bigint)[] = [
...singlesValues,
0.125, 0.375, 3.5, 9.5, 99.5, 0.95, 9.995, 1.25, 1.35, 2.675, 9.999999e-5, 1e15, 9999999e9, 1e-7, 123456.79, 16777215, 7.006492e-46, 1.17549421e-38, 8388609,
];
const singlesFormatsOp = op("singles::formats");
const singlesParsesOp = op("singles::parses");
const singlesFormats = [
...singlesFormatted.flatMap((a) => Array.from({ length: 12 }, (_, p) => Uint8Array.of(singlesFormatsOp, 0, ...single(a), p))),
...singlesFormatted.flatMap((a) => [1, 3, 4].map((kind) => Uint8Array.of(singlesFormatsOp, kind, ...single(a), 0))),
...singlesFormatted.flatMap((a) => [[1, 2], [2, 1], [NaN, 1], [1, NaN], [-0, 0], [0, -0]].map(([b, c]) => Uint8Array.of(singlesFormatsOp, 2, ...single(a), ...single(b), ...single(c)))),
...singlesValues.map((a) => Uint8Array.of(singlesParsesOp, 2, ...single(a))),
];

/**
* `singles::parses`' texts: std's grammar, past each end of a single's range, and
* decimals within a hair of halfway between two singles, which rounding through the
* nearest double would round twice.
*/
const singlesParsed = [
"", "+", "-", ".", "e5", "1e", "1.e5", ".5", "+1", "1_000", " 1", "1e40", "-1e40", "1e-50", "-0", "inf", "-infinity", "NaN", "nan1",
"3.4028235e38", "3.4028236e38", "3.40282357e38", "3.4028235677973366e38", "3.4028235677973367e38", "1.4e-45", "7e-46",
"7.006492321624085354618647916449580656401e-46", "7.006492321624085354618647916449580656402e-46", "1.1754942e-38", "1.17549435e-38",
"16777217", "16777217.000000000001", "16777218.99999999999", "16777219", "1.00000005960464477539062499", "1.000000059604644775390625",
"1.00000005960464477539062501", "1.0000000596046448", "0.1", "1e10", "1234567e-10", "12345678e-10", "9007199254740993",
`1${"0".repeat(60)}e-60`, `0.${"0".repeat(60)}1e61`, "1e65535", "1e-65536",
].map((text) => Uint8Array.of(singlesParsesOp, 0, ...new TextEncoder().encode(text)));

/** How many ULPs from native Rust's each float of `floats`' and `singles`' kind 7 may be, which Rust leaves to libm. */
const ULPS = 2n;

export function within(input: Uint8Array, native: Uint8Array, translated: Uint8Array): boolean {
if (input[0] % count !== floatsOp || input[1] % 10 !== 7 || native.length !== translated.length) return false;
const width = input[0] % count === floatsOp ? 8 : input[0] % count === singlesOp ? 4 : 0;
if (width === 0 || input[1] % 10 !== 7 || native.length !== translated.length) return false;
const [a, b] = [native, translated].map((v) => new DataView(v.buffer, v.byteOffset, v.length));
// Bits as a line through zero, so the distance between two is how many doubles apart they are.
const line = (v: bigint) => (v < 0n ? -(v & 0x7fffffffffffffffn) : v);
for (let i = 0; i < native.length; i += 8) {
const d = line(a.getBigInt64(i)) - line(b.getBigInt64(i));
const read = (v: DataView, i: number) => (width === 8 ? v.getBigInt64(i) : BigInt(v.getInt32(i)));
// Bits as a line through zero, so the distance between two is how many floats apart they are.
const magnitude = (1n << BigInt(width * 8 - 1)) - 1n;
const line = (v: bigint) => (v < 0n ? -(v & magnitude) : v);
for (let i = 0; i < native.length; i += width) {
const d = line(read(a, i)) - line(read(b, i));
if (d > ULPS || d < -ULPS) return false;
}
return true;
Expand Down Expand Up @@ -411,4 +476,4 @@ const iters = Array.from({ length: 12 }, (_, kind) => [[], [2, 7, 9, 14], [0, 2,
const hintedOp = op("strs::hinted");
const hinted = Array.from({ length: 10 * 7 * 21 }, (_, i) => Uint8Array.of(hintedOp, i % 10, Math.floor(i / 10) % 7, Math.floor(i / 70)));

export const cases: Uint8Array[] = [...loops, ...durations, ...ints(), ...utf8, ...floats, ...formats, ...parsed, ...dyns, ...cells, ...shared, ...locks, ...onces, ...bounds, ...unsafeCells, ...edits, ...subslices, ...consumers, ...closures, ...elements, ...ranges, ...reducers, ...sliceEdits, ...drains, ...iters, ...hinted];
export const cases: Uint8Array[] = [...loops, ...durations, ...ints(), ...utf8, ...floats, ...formats, ...parsed, ...singlesCases, ...singlesFormats, ...singlesParsed, ...dyns, ...cells, ...shared, ...locks, ...onces, ...bounds, ...unsafeCells, ...edits, ...subslices, ...consumers, ...closures, ...elements, ...ranges, ...reducers, ...sliceEdits, ...drains, ...iters, ...hinted];
7 changes: 7 additions & 0 deletions ts/fixtures/lang/expected/core/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ export function unwrapOr$0k15iai(self: rt.char | undefined, default_: rt.char):
return self;
}

export function unwrapOr$19h8ynp(self: rt.f32 | undefined, default_: rt.f32): rt.f32 {
if (self === undefined) {
return default_;
}
return self;
}

export function unwrapOr$1ayoj7n(self: rt.f64 | undefined, default_: rt.f64): rt.f64 {
if (self === undefined) {
return default_;
Expand Down
3 changes: 2 additions & 1 deletion ts/fixtures/lang/expected/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { orders, bytes as orders_bytes } from "./orders.ts";
import { lists, shared as pointers_shared, places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound, writers, made } from "./pointers.ts";
import { refs, toRefs } from "./refs.ts";
import { copies as shapes_copies, order, discriminants, loops, patterns, uninhabited, closures, library, flows } from "./shapes.ts";
import { singles, formats as singles_formats, parses as singles_parses } from "./singles.ts";
import { parts, chunked, subslices, drains, ranges as slices_ranges, edits as slices_edits } from "./slices.ts";
import { splits } from "./splits.ts";
import { statics, cells as statics_cells, onces } from "./statics.ts";
Expand Down Expand Up @@ -182,7 +183,7 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec)
asSlices(data66, out67);
}] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::widenings", widenings] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::uninhabited", (_2167: rt.u8, data67: rt.ByteSlice, out68: rt.ByteVec): void => {
uninhabited(data67, out68);
}] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::lent_keys", lentKeys] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::lent_values", lentValues] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void]];
}] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::lent_keys", lentKeys] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::lent_values", lentValues] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["singles::singles", singles] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["singles::formats", singles_formats] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["singles::parses", singles_parses] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void]];
}

/** Run one case. */
Expand Down
22 changes: 22 additions & 0 deletions ts/fixtures/lang/expected/singles.Gauge.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";

export function eq$PartialEq(self: Gauge, other: Gauge): boolean {
if (!(self.level === other.level)) {
return false;
}
return self.peak === other.peak;
}

export function default$Default(): Gauge {
return { level: 0.0, peak: undefined };
}

/** A struct with an `f32` field, changed in place through a `&mut`. */
export interface Gauge {
level: rt.f32;
peak: rt.f32 | undefined;
}

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

import * as rt from "./rt.ts";

export function fmt$Debug(self: Reading, f: rt.Formatter): rt.Result<undefined, rt.FmtError> {
const _3: string = "Reading";
const _4: string = "level";
const _5: rt.Fmt = (f: rt.Formatter) => rt.f32Debug(self.level, f);
const _7: string = "history";
const _8: rt.Fmt = (f: rt.Formatter) => rt.fmtList(self.history, f, rt.f32Debug);
return rt.debugStructFinish(rt.debugStructField(rt.debugStructField(rt.fmtDebugStruct(f, _3), _4, _5), _7, _8));
}

/** A reading whose derived `Debug` writes each `f32` with the placeholder's options. */
export interface Reading {
level: rt.f32;
history: rt.f32[];
}

//# sourceMappingURL=singles.Reading.ts.map
Loading