diff --git a/ffi/README.md b/ffi/README.md index f9ee314dd..bb0ae84b4 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -64,7 +64,7 @@ re-exports its items instead, which export. A type no path reaches is refused. | Rust | C | | --- | --- | | a struct whose fields are all `pub`, not `#[non_exhaustive]`, which map, with no `Drop` of its own | a record: a struct, copied across | -| an enum whose fields map, with no `Drop` of its own | a C enum without fields, or a struct of a tag and a union | +| an enum with variants whose fields map, with no `Drop` of its own | a C enum without fields, or a struct of a tag and a union | | any other type, if `Send` | an object: an opaque handle to an `Arc`, freed with `_free` | | a type whose only generic parameters are lifetimes, such as `Path<'a>` | an object holding its `'static` form | | a generic type, at each instance it infers | that instance as any other type, with each method whose impl and own bounds it meets; any other instance is refused | @@ -72,6 +72,7 @@ re-exports its items instead, which export. A type no path reaches is refused. | a `pub` trait | a callback interface: a table of C functions and their data; a trait C cannot implement, such as one with a blanket impl, an associated type, or a supertrait but a marker, is left out, since C never passes one, and a signature taking one from C is refused | | a type parameter or `impl` bounded by `Fn`, `FnMut`, or `FnOnce`, which no lifetime bound or return lets outlive the call | a C function and its `void *` data, which Rust calls only during the call; any other closure is refused | | `fn` and inherent `pub` methods | functions named through their type | +| a `()` parameter of a function, not of a callback's method | nothing: C passes no argument for it | | `Result` | `bool f(..., T *out, E *err)`; an `E` of another crate that implements `Display`, such as `anyhow::Error` or `io::Error`, is `String`, its message | | a returned tuple `(A, B)` | one out pointer each: `void f(..., A *out0, B *out1)` | | a tuple inside another type, or as a field or parameter | `_tuple__`: a struct of fields `_0`, `_1`, and so on; of references only returned | @@ -79,6 +80,7 @@ re-exports its items instead, which export. A type no path reaches is refused. | a function returning `Poll` that polls through a `&Context`, a `&mut Context`, or a reference to an object exactly one associated function makes from a `Waker` alone, of a type with no generic parameters, such as kio's `Waiter` | a future of `T`, as an `async fn`'s: each poll calls the function again with the poll's context, or with an object made from its waker and kept until the next poll, so what it registers stays registered; each other parameter taken by value must be `Copy` | | `poll_x`, when `x` exports as a future | left out: the future is its poll | | `bool`, integers but 128-bit, floats, `char` | `bool`, `` types, `float`, `double`, a `uint32_t` code point | +| `u128` | `_u128`: its low and high 64 bits | | `&str`, `String`, `&[u8]`, `Vec`, `bytes::Bytes` | `_buf`: a view passed in, an owned buffer returned | | `&Path`, `PathBuf` | `_buf` of the path's bytes as the OS holds them on Unix, which need not be UTF-8, else UTF-8 | | `Duration` | `_duration` | @@ -100,17 +102,19 @@ re-exports its items instead, which export. A type no path reaches is refused. | `impl IntoIterator`, where `X` is no type parameter | `Vec`, or `&[Y]` where `X` is `&Y` | | `impl io::Read` | `&[u8]` | | another type parameter | `serde_json::Value` for a serde `Serialize` or `Deserialize` bound, else the first of `String`, `Bytes`, and `Vec` meeting its bounds, or a foreign implementation of its one exported trait; else an instance of the function at each type the exports implement a trait bounding it for, through a blanket impl at each type implementing what the impl requires, as `&X` where that meets its bounds too and no bound, its own or one a supertrait implies, makes it outlive the call, so C keeps its handle, and, as an argument of a generic type, at each argument of an instance that meets its bounds and makes each generic type the signature names an instance | +| a const parameter of a function | an instance of the function at each value an instance of a generic type the signature passes it to has there, where each generic type the signature then names is an instance | +| a const argument | a `bool` or an integer below 128 bits; any other is refused | A generic type infers the instances it exports at, with no config: the one at its -defaults, if each type parameter has one; the one each inherent impl is of, such as -`impl Pair`; each choice, for each parameter, among its default and the types the -exports implement a trait bounding it for, one of the exports', which are `()`, a -primitive, `String`, or an exported type or an instance of one, such as -`impl Seed for Wrap`; and each instance a signature, `pub` field, or constant -names at any choice its bounds allow, whether or not the rest of it maps, which may name -more. Inference finishes before any type is mapped, so mapping never adds an instance. -An instance that holds an instance of a type inside it, such as `Cell>`, is -refused, as it could name ever deeper ones. +defaults, if each type and const parameter has one, as `frame::Buffer` at `N = 8`; the +one each inherent impl is of, such as `impl Pair`; each choice, for each parameter, +among its default and the types the exports implement a trait bounding it for, one of +the exports', which are `()`, a primitive, `String`, or an exported type or an instance +of one, such as `impl Seed for Wrap`; and each instance a signature, `pub` +field, or constant names at any choice its bounds allow, whether or not the rest of it +maps, which may name more. Inference finishes before any type is mapped, so mapping +never adds an instance. An instance that holds an instance of a type inside it, such as +`Cell>`, is refused, as it could name ever deeper ones. A record or enum holds only values and `Copy` handles: one with a field that does not map, or that holds itself, is an object instead, and the census lists it. A borrowed return is @@ -212,8 +216,10 @@ else is refused, and the census counts why. `Point`, and `api_Counter_seeded_polar_Point` at `polar::Point`. The names depend on every instance the item exports at, so one an upstream change adds can lengthen another's name. A type instance's methods are named through it, and a function instance appends - its arguments' names, a reference's by what it borrows. An `exclude` of a type or method - by the generic type's path leaves it out of every instance, and by the instance's path + its arguments' names, a reference's by what it borrows and a const's by its value: the + api fixture's `drain` is `api_drain_4` at `Batch` and `api_drain_2` at `Batch<2>`. An + `exclude` of a type or method by the generic type's path leaves it out of every + instance, and by the instance's path (`"mux::catalog::Producer::stats"`) out of that one. ## C++ @@ -268,8 +274,8 @@ exceptions and RTTI on or off: it throws nothing and reads no type information. lives only as long as the bytes it views, and a `view` lent to a callback only for the call; reading one past that is C++'s own undefined behavior, which Clang's `-Wdangling` refuses for a view or `str()` of a temporary `buf`. A `Vec` is a `std::vector`, of - `view`s for bytes passed in, and a socket address `socket_addr`, the C struct. `Option` is `std::optional`, a - tuple `std::tuple`, and `Result` a `result`, whose `value()` aborts on an + `view`s for bytes passed in, a socket address `socket_addr`, the C struct, and a `u128` + `u128`, the C struct. `Option` is `std::optional`, a tuple `std::tuple`, and `Result` a `result`, whose `value()` aborts on an error. - **Traits** are abstract classes, passed as a `std::unique_ptr` that Rust deletes. - **Closures** lent for a call are any callable, taken by a template parameter, which @@ -329,7 +335,7 @@ in as a `ByteArray`, bytes returned as their owner, and anything else encoded in record holding bytes does too; Rust's lent ones are copied across in one call to compare. Strings are `String`, `usize` `ULong` and `isize` `Long`, which abort past a 32-bit target's range, `Option` `T?`, a tuple `Pair`, - `Triple`, or else a generated `Tuple`, though a tuple of one returned whole is its element, `Duration` `kotlin.time.Duration`, and an `Err` a thrown `Exception` + `Triple`, or else a generated `Tuple`, though a tuple of one returned whole is its element, `u128` `java.math.BigInteger`, which throws `IllegalArgumentException` passed in out of its range, `Duration` `kotlin.time.Duration`, and an `Err` a thrown `Exception` holding it, whose message is its `Display`. - A **path** is a `String`: one Rust returns that is not UTF-8 throws. A `SocketAddr`, `SocketAddrV4`, or `SocketAddrV6` is a `java.net.InetSocketAddress`, which must be @@ -386,7 +392,7 @@ caller imports it with no ignores. place, and others, which Python may change while Rust reads, are copied. Bytes Rust returns, or a record holds, are a read-only `memoryview` lending Rust's buffer until it and its views are released. Those Rust lends a closure or trait method are copied into - `bytes`. Strings are `str`, `Option` `T | None`, a tuple a `tuple`, `Duration` + `bytes`. Strings are `str`, `u128` an `int`, `Option` `T | None`, a tuple a `tuple`, `Duration` `datetime.timedelta`, truncated to the microsecond, with `timedelta.max` for any longer and for `Duration::MAX`, a `Range` a `range`, and an `Err` a raised `Exception` holding it in `error`, whose `str` is its `Display`. @@ -443,7 +449,8 @@ in either language mode. another value, they are copied in. Bytes Rust returns lend its buffer until Swift releases the `Data`, though `Data` copies a few bytes inline, freeing Rust's at once. Strings are `String`, copied, `char` `Unicode.Scalar`, `usize` `UInt`, `Option` `T?`, - a tuple a Swift tuple, though a tuple of one is its element, and `Duration` + a tuple a Swift tuple, though a tuple of one is its element, `u128` a `U128` of its + `high` and `low` halves until the deployment target allows `UInt128`, and `Duration` `Swift.Duration`. An `Err` is thrown: an exported type as itself, which conforms to `Swift.Error`, and any other in a generated `Error` holding it. - A **path** is a `String`: one Rust returns that is not UTF-8 traps. A `SocketAddr`, @@ -534,9 +541,10 @@ reads a go.mod. returns freed memory, such as [mimalloc](https://crates.io/crates/mimalloc), declared in the exported crate, holds it near what is lent. `Copy()` copies it out. Text Rust returns is copied once into a `string`, and Rust's freed at the call. A path is a `string` - of the OS's bytes, which need not be UTF-8. `Option` is `*T`, or for an object its - pointer, nil for `None`. A tuple is `rt.Tuple2[A, B]` and so on, but a tuple of one is its - element, and one returned whole is the function's results. `Duration` is + of the OS's bytes, which need not be UTF-8. `Option` is `*T`, or for an object or + `u128` its pointer, nil for `None`. A tuple is `rt.Tuple2[A, B]` and so on, but a tuple of one is its + element, and one returned whole is the function's results. A `u128` is a `*big.Int`, which + panics passed in out of its range. `Duration` is `time.Duration`: a negative one passed in, or one Rust returns past its 292 years, panics. A socket address is a `netip.AddrPort`, whose zone names an IPv6 scope by its number or its interface's name; one Rust returns with an IPv6 flow label, which @@ -604,7 +612,8 @@ the library's own URI, mapped to the library by a build hook or by `--native-ass returns is copied into a `String`. A path is a `String`, and one Rust returns that is not UTF-8 aborts, since a throw would leave the rest of a list or record being lifted unfreed. `Option` is `T?`, a tuple a Dart record, and a `Vec` or `&[T]` a - `List`. `Duration` is `Duration`, truncated to the microsecond: a negative one passed + `List`. A `u128` is a `BigInt`, which throws an `ArgumentError` passed in out of + its range. `Duration` is `Duration`, truncated to the microsecond: a negative one passed in throws an `ArgumentError`, and Rust's longest is Dart's. A socket address is a `SocketAddr` of an `InternetAddress`, a port, and IPv6's flow label and scope; one passed in of the wrong family throws an `ArgumentError`, and a port, label, or scope out of its diff --git a/ffi/fixtures/api/examples/transcript.rs b/ffi/fixtures/api/examples/transcript.rs index 026b29bae..15c894126 100644 --- a/ffi/fixtures/api/examples/transcript.rs +++ b/ffi/fixtures/api/examples/transcript.rs @@ -205,6 +205,31 @@ fn main() { ); println!("total {}", api::total(2..5)); + println!("reach {}", api::reach(2, (), 5)); + let scaled = |v, by| api::scaled(v, by).map_or("none".to_string(), |v| format!("{v:032x}")); + println!( + "scaled {} {} {}", + scaled(u128::MAX, 1), + scaled((1 << 64) + 1, 1 << 63), + scaled(1 << 127, 2) + ); + let mut batch: api::Batch = api::Batch::new(); + let accepted = (1..=5).filter(|&v| batch.push(v)).count(); + let mut batch2 = api::pair(); + let paired = (1..=3).filter(|&v| batch2.push(v)).count(); + let emptied = api::drain(&mut batch); + println!( + "batch {} {accepted} {emptied} {} {} {paired} {}", + batch.capacity(), + api::drain(&mut batch), + batch2.capacity(), + api::drain(&mut batch2) + ); + println!( + "capacities {} {}", + api::capacities(&batch, &batch2), + api::capacities(&batch2, &batch2) + ); let wide = api::widen(2..5, 1); println!("widen {} {}", wide.start, wide.end); println!( @@ -309,6 +334,16 @@ fn main() { Poll::Ready(Err(e)) => println!("nonempty err {e}"), Poll::Pending => println!("nonempty pending"), } + let mut filled: Vec = Vec::new(); + let mut fill4: api::Batch = api::Batch::new(); + if let Poll::Ready(v) = pin!(api::fill(&mut fill4, 9)).poll(&mut cx) { + filled.extend(v.iter().map(u32::to_string)); + } + let mut fill2 = api::pair(); + if let Poll::Ready(v) = pin!(api::fill(&mut fill2, 1)).poll(&mut cx) { + filled.extend(v.iter().map(u32::to_string)); + } + println!("fill {}", filled.join(" ")); // The send happens at the call: the future is dropped unpolled. drop(channel.send_later(Bytes::from_static(b"eager"))); @@ -331,7 +366,7 @@ fn main() { if let Poll::Ready(Ok(len)) = channel.clone().poll_measure(&mut cx, 2) { println!("measure {len}"); } - channel.listen(&cx); + channel.listen((), &cx); channel.send(Bytes::from_static(b"heard")); println!("woken {}", count.0.load(Ordering::Relaxed)); diff --git a/ffi/fixtures/api/src/lib.rs b/ffi/fixtures/api/src/lib.rs index 47fc677bd..1a9201ec2 100644 --- a/ffi/fixtures/api/src/lib.rs +++ b/ffi/fixtures/api/src/lib.rs @@ -480,6 +480,63 @@ pub fn total(range: Range) -> u64 { range.sum() } +/// How far `start` reaches to `end`, past a `()` C does not pass. +pub fn reach(start: u32, _: (), end: u32) -> u32 { + end - start +} + +/// `v` times `by`, unless past `u128::MAX`. +pub fn scaled(v: u128, by: u64) -> Option { + v.checked_mul(by.into()) +} + +/// Up to `N` numbers, as moq's `frame::Buffer` holds up to `N` frames: a const parameter +/// with a default, which exports at it and at each other value a signature names. +pub struct Batch(Vec); + +impl Batch { + /// An empty batch. + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Append `v`, unless the batch already holds `N`. + pub fn push(&mut self, v: u32) -> bool { + let room = self.0.len() < N; + if room { + self.0.push(v); + } + room + } + + /// How many numbers it holds at most. + pub fn capacity(&self) -> usize { + N + } +} + +/// A batch of two, an instance at a const argument other than the default. +pub fn pair() -> Batch<2> { + Batch::new() +} + +/// The sum of a batch's numbers, draining it: a const parameter at each instance's value. +pub fn drain(batch: &mut Batch) -> u64 { + batch.0.drain(..).map(u64::from).sum() +} + +/// Both batches' capacities: two const parameters, each at every instance's value. +pub fn capacities(a: &Batch, b: &Batch) -> usize { + a.capacity() + b.capacity() +} + +/// Fill a batch with `v` until it holds `N`, lending what it holds: a future whose result +/// borrows a parameter, as moq's `group::Consumer::read_frames` does. +pub async fn fill(batch: &mut Batch, v: u32) -> &mut [u32] { + while batch.push(v) {} + &mut batch.0 +} + /// A range, widened by `by` at each end. pub fn widen(range: Range, by: u64) -> Range { range.start.saturating_sub(by)..range.end + by @@ -641,8 +698,8 @@ impl Channel { } /// Wakes the context's task on the next send, as `notify` wakes its waker: C passes the - /// waker the context is made of. - pub fn listen(&self, cx: &Context<'_>) { + /// waker the context is made of, and nothing for the `()` before it. + pub fn listen(&self, _: (), cx: &Context<'_>) { self.state.lock().unwrap().1 = Some(cx.waker().clone()); } @@ -1110,6 +1167,10 @@ pub fn walk(steps: u64, mut visit: impl FnMut(&Cursor) -> u64) -> u64 { (0..steps).map(|at| visit(&Cursor { at })).sum() } +/// An enum without variants: no value is one, so C never holds one, as an object no export +/// returns, and the bindings leave it out. +pub enum Void {} + /// An object no export returns but its own method, so C never holds one: left out of the /// bindings, with what takes it, and listed in the census. pub struct Lease(u8); diff --git a/ffi/fixtures/reject/expected.txt b/ffi/fixtures/reject/expected.txt index 55c5cf817..7e1e02b0d 100644 --- a/ffi/fixtures/reject/expected.txt +++ b/ffi/fixtures/reject/expected.txt @@ -13,10 +13,11 @@ fixtures/reject/src/lib.rs:183:1: rust2-ffi: cannot export instance `Grow<(u32,) fixtures/reject/src/lib.rs:186:2: rust2-ffi: cannot export method `Grow::grow`: refused type `reject::Grow<(u32,)>`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:197:1: rust2-ffi: cannot export instance `Shell<(u32,)>`: type argument `(u32,)`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:206:1: rust2-ffi: cannot export fn `shell`: refused type `reject::Shell<(u32,)>`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:217:1: rust2-ffi: cannot export fn `probe`: const generic; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:217:1: rust2-ffi: cannot export fn `probe`: const generic `N`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:222:1: rust2-ffi: cannot export fn `excluded_cell`: excluded type `reject::Cell`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:227:1: rust2-ffi: cannot export fn `listed`: refused type `reject::Cell>`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:235:1: rust2-ffi: cannot export fn `fixed`: const generic `reject::Fixed`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:232:1: rust2-ffi: cannot export instance `Fixed<'a'>`: const argument `'a'`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:235:1: rust2-ffi: cannot export fn `fixed`: refused type `reject::Fixed<'a'>`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:23:1: rust2-ffi: cannot export fn `borrowing`: borrowing future from a function that is not async; exclude it in the config to leave it out fixtures/reject/src/lib.rs:240:9: rust2-ffi: cannot export use `HashSet`: re-export of the standard library's item `std`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:243:1: rust2-ffi: cannot export fn `lend`: Waker outside a parameter taken by value; exclude it in the config to leave it out diff --git a/ffi/fixtures/reject/src/lib.rs b/ffi/fixtures/reject/src/lib.rs index de34977a3..90de6f5a4 100644 --- a/ffi/fixtures/reject/src/lib.rs +++ b/ffi/fixtures/reject/src/lib.rs @@ -211,7 +211,7 @@ pub fn shell() -> Shell<(u32,)> { } /// A const generic function whose bound names an instance nothing else does: refused at -/// its const parameter, before any candidate for the other is mapped. +/// its const parameter, which nothing names, before any candidate for the other is mapped. pub struct Probe(T); pub fn probe(x: impl Into>) { @@ -228,11 +228,11 @@ pub fn listed(cell: Cell>) -> usize { cell.0.len() } -/// A const-generic type, whose const argument the IR cannot carry. -pub struct Fixed(pub u8); +/// A type whose const argument is a `char`, which only a `bool` or integer may be. +pub struct Fixed(pub u8); /// An instance of it, which a signature names. -pub fn fixed() -> Fixed<2> { +pub fn fixed() -> Fixed<'a'> { Fixed(2) } diff --git a/ffi/moq/ffi.toml b/ffi/moq/ffi.toml index 308b88c8b..9d54055f2 100644 --- a/ffi/moq/ffi.toml +++ b/ffi/moq/ffi.toml @@ -14,11 +14,11 @@ exclude = [ "clock::Rng", "clock::timeout", "clock::timeout_at", + "field@mux::codec::flac::Config::md5", "field@tokio::Log::level", + "frame::Buffer::drain", "frame::Producer::group", "group::Consumer::poll_read_frames", - "group::Consumer::read_frames", - "group::Producer::write_frames", "hang::catalog::Audio::enabled", "hang::catalog::Video::enabled", "hang::container::Frame::encode", diff --git a/ffi/runtime/ffi_cgo.go b/ffi/runtime/ffi_cgo.go index c7d8df558..5f90cef8a 100644 --- a/ffi/runtime/ffi_cgo.go +++ b/ffi/runtime/ffi_cgo.go @@ -1,5 +1,5 @@ // What each generated package converts its C types with, which cgo makes its own, after -// the generated code names the export's C types as cBuf, cWaker, cDuration, and cAddr, its +// the generated code names the export's C types as cBuf, cWaker, cU128, cDuration, and cAddr, its // buffer's free as cBufFree, and the runtime's callbacks into Go as goCall and goRelease: // the arena a call lowers nested values into, values across the C ABI, and what a future // waits on. @@ -105,6 +105,26 @@ func lentBytesOption_(buf cBuf) *[]byte { return &b } +// lowerU128_ is a u128 as Rust holds it, from 0 through 2^128 - 1. +func lowerU128_(v *big.Int) cU128 { + if v.Sign() < 0 || v.BitLen() > 128 { + panic("an int out of range for Rust's u128") + } + var b [16]byte + v.FillBytes(b[:]) + var hi, lo uint64 + for i := range 8 { + hi = hi<<8 | uint64(b[i]) + lo = lo<<8 | uint64(b[8+i]) + } + return cU128{lo: C.uint64_t(lo), hi: C.uint64_t(hi)} +} + +func liftU128_(c cU128) *big.Int { + hi := new(big.Int).SetUint64(uint64(c.hi)) + return hi.Lsh(hi, 64).Or(hi, new(big.Int).SetUint64(uint64(c.lo))) +} + // lowerDuration_ is a Duration as Rust holds it, which has no negative one. func lowerDuration_(d time.Duration) cDuration { if d < 0 { diff --git a/ffi/runtime/jni.rs b/ffi/runtime/jni.rs index 93d36bed4..5f925e913 100644 --- a/ffi/runtime/jni.rs +++ b/ffi/runtime/jni.rs @@ -732,6 +732,17 @@ where } } +impl Wire for rt::U128 { + fn read(r: &mut Reader<'_>) -> Self { + Self { lo: r.get(), hi: r.get() } + } + + fn write(self, w: &mut Writer) { + w.put(self.lo); + w.put(self.hi); + } +} + impl Wire for rt::Duration { fn read(r: &mut Reader<'_>) -> Self { Self { diff --git a/ffi/runtime/python.rs b/ffi/runtime/python.rs index dc353c1c2..fa1f73080 100644 --- a/ffi/runtime/python.rs +++ b/ffi/runtime/python.rs @@ -604,6 +604,39 @@ pub fn uint_out(value: u64) -> R { unsafe { Obj::new(PyLong_FromUnsignedLongLong(value)) } } +/// A `u128`, from any `int` or object with `__index__` in its range. +pub fn u128_in(obj: *mut PyObject) -> R { + // SAFETY: the GIL is held, and `obj` is live. + unsafe { + let index = Obj::new(PyNumber_Index(obj))?; + let shift = uint_out(64)?; + let high = Obj::new(PyNumber_Rshift(index.ptr(), shift.ptr()))?; + // A negative int's high half is negative, which raises here as one past `u64` does. + let hi = PyLong_AsUnsignedLongLong(high.ptr()); + if hi == u64::MAX && !PyErr_Occurred().is_null() { + PyErr_Clear(); + return overflow("int out of range for Rust's u128"); + } + let lo = PyLong_AsUnsignedLongLongMask(index.ptr()); + if lo == u64::MAX && !PyErr_Occurred().is_null() { + return Err(Raised); + } + Ok(rt::U128 { lo, hi }) + } +} + +pub fn u128_out(value: rt::U128) -> R { + let lo = uint_out(value.lo)?; + if value.hi == 0 { + return Ok(lo); + } + // SAFETY: the GIL is held, and each operand is live. + unsafe { + let high = Obj::new(PyNumber_Lshift(uint_out(value.hi)?.ptr(), uint_out(64)?.ptr()))?; + Obj::new(PyNumber_Or(high.ptr(), lo.ptr())) + } +} + pub fn float_in(obj: *mut PyObject) -> R { // SAFETY: the GIL is held, and `obj` is live. let value = unsafe { PyFloat_AsDouble(obj) }; diff --git a/ffi/runtime/rt.dart b/ffi/runtime/rt.dart index 08d8246be..2d41f0b54 100644 --- a/ffi/runtime/rt.dart +++ b/ffi/runtime/rt.dart @@ -178,6 +178,13 @@ final class _Buf extends ffi.Struct { external ffi.Pointer owner; } +final class _U128 extends ffi.Struct { + @ffi.Uint64() + external int lo; + @ffi.Uint64() + external int hi; +} + final class _Duration extends ffi.Struct { @ffi.Uint64() external int secs; @@ -595,6 +602,18 @@ final class _Native { String scalar(int code) => String.fromCharCode(code); + void u128(_U128 dst, BigInt value) { + if (value.isNegative || value.bitLength > 128) { + throw ArgumentError.value(value, 'value', "out of range for Rust's u128"); + } + // Each half as the int that holds its 64 bits. + dst.lo = value.toSigned(64).toInt(); + dst.hi = (value >> 64).toSigned(64).toInt(); + } + + BigInt u128Of(_U128 c) => + BigInt.from(c.hi).toUnsigned(64) << 64 | BigInt.from(c.lo).toUnsigned(64); + /// Rust's `Duration::MAX`, and any longer than Dart's longest, is that. static const _longest = Duration(microseconds: 0x7fffffffffffffff); diff --git a/ffi/runtime/rt.kt b/ffi/runtime/rt.kt index 95ae6521b..211d73490 100644 --- a/ffi/runtime/rt.kt +++ b/ffi/runtime/rt.kt @@ -96,6 +96,13 @@ class Writer { socketAddr(v) } + /** A `u128`, from 0 through 2^128 - 1. */ + fun u128(v: java.math.BigInteger) { + kotlin.require(v.signum() >= 0 && v.bitLength() <= 128) { "$v is out of range for Rust's u128" } + i64(v.toLong()) + i64(v.shiftRight(64).toLong()) + } + fun duration(v: kotlin.time.Duration) { kotlin.require(!v.isNegative()) { "a negative duration has no Rust Duration" } v.toComponents { secs, nanos -> @@ -194,6 +201,12 @@ class Reader(bytes: ByteArray) { return java.net.InetSocketAddress(address, port) } + fun u128(): java.math.BigInteger { + val lo = b.getLong() + val hi = b.getLong() + return java.math.BigInteger(1, java.nio.ByteBuffer.allocate(16).putLong(hi).putLong(lo).array()) + } + fun duration(): kotlin.time.Duration { val secs = b.getLong() val nanos = b.getInt() diff --git a/ffi/runtime/rt.rs b/ffi/runtime/rt.rs index c3163041a..97c7292bf 100644 --- a/ffi/runtime/rt.rs +++ b/ffi/runtime/rt.rs @@ -204,6 +204,27 @@ impl Buf { } } +/// `u128`, as its two halves. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct U128 { + pub lo: u64, + pub hi: u64, +} + +impl U128 { + pub fn lift(self) -> u128 { + u128::from(self.hi) << 64 | u128::from(self.lo) + } + + pub fn lower(v: u128) -> Self { + Self { + lo: v as u64, + hi: (v >> 64) as u64, + } + } +} + #[repr(C)] #[derive(Clone, Copy)] pub struct Duration { diff --git a/ffi/runtime/rt.swift b/ffi/runtime/rt.swift index fa23883d4..ec2107f2d 100644 --- a/ffi/runtime/rt.swift +++ b/ffi/runtime/rt.swift @@ -20,6 +20,38 @@ public struct SocketAddr: Hashable, Sendable { } } +/// A Rust `u128`, as its two halves, until the deployment target allows Swift's `UInt128`. +public struct U128: Hashable, Comparable, Sendable, CustomStringConvertible { + public var high: UInt64 + public var low: UInt64 + + public init(high: UInt64, low: UInt64) { + self.high = high + self.low = low + } + + public init(_ value: UInt64) { + self.init(high: 0, low: value) + } + + public static func < (a: U128, b: U128) -> Bool { + (a.high, a.low) < (b.high, b.low) + } + + /// Its decimal digits. + public var description: Swift.String { + if high == 0 { + return Swift.String(low) + } + // Split off the last 19 digits, as 10^19 is the largest power of ten a UInt64 holds. + let base: UInt64 = 10_000_000_000_000_000_000 + let (q, r) = high.quotientAndRemainder(dividingBy: base) + let (lowQ, digits) = base.dividingFullWidth((r, low)) + let tail = Swift.String(digits) + return U128(high: q, low: lowQ).description + Swift.String(repeating: "0", count: 19 - tail.count) + tail + } +} + extension Native { /// Checked before any other call: a Swift file and a library from different builds /// must not mix. @@ -182,6 +214,14 @@ extension Native { return half * 2 + .seconds(Int64(value.secs & 1)) + .nanoseconds(Int64(value.nanos)) } + static func u128(_ value: U128) -> CU128 { + CU128(lo: value.low, hi: value.high) + } + + static func u128(_ value: CU128) -> U128 { + U128(high: value.hi, low: value.lo) + } + static func socketAddr(_ addr: SocketAddr) -> CAddr { let v6: Bool switch addr.ip.count { diff --git a/ffi/src/abi.rs b/ffi/src/abi.rs index 392af55c7..f2b5715ae 100644 --- a/ffi/src/abi.rs +++ b/ffi/src/abi.rs @@ -14,7 +14,7 @@ use std::collections::{BTreeMap, HashMap}; use rustc_span::Span; -use crate::ir::{Access, Api, Dyn, Function, Home, Int, Receiver, Type}; +use crate::ir::{Access, Api, Dyn, Function, Home, Int, Literal, Receiver, Type}; /// A C name claimed twice: the name, then what claimed it first and second. pub type Clash = (String, (String, Span), (String, Span)); @@ -31,6 +31,8 @@ pub enum Ty { Char, /// `_buf`: a view of bytes passed in, an owned buffer returned. Buf, + /// `_u128` + U128, /// `_duration` Duration, /// `_socket_addr` @@ -354,6 +356,7 @@ impl<'a> Abi<'a> { Type::F32 => "f32".to_string(), Type::F64 => "f64".to_string(), Type::Char => "char".to_string(), + Type::U128 => "u128".to_string(), Type::Duration => "duration".to_string(), Type::SocketAddr(_) => "socket_addr".to_string(), Type::Option(inner) => format!("option_{}", self.key(inner)), @@ -369,6 +372,9 @@ impl<'a> Abi<'a> { Type::Bound(int) => format!("bound_{}", int.rust()), Type::Bounds(int) => format!("bounds_{}", int.rust()), Type::Ref(_, inner) | Type::Box(inner) => self.key(inner), + // Only an instance's name holds one, where a C identifier has no `-`. + Type::Const(Literal::Int(_, v)) if *v < 0 => format!("neg{}", v.unsigned_abs()), + Type::Const(value) => value.to_string(), other => unreachable!("no option struct holds {other:?}"), } } @@ -405,6 +411,7 @@ impl<'a> Abi<'a> { /// The C type of `ty` as a value: a parameter, field, or what a function returns. pub fn ty(&self, ty: &Type) -> Ty { match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => Ty::Void, Type::Bool => Ty::Bool, Type::Int(int) => Ty::Int(*int), @@ -412,6 +419,7 @@ impl<'a> Abi<'a> { Type::F64 => Ty::F64, Type::Char => Ty::Char, Type::Str | Type::String | Type::Slice | Type::Vec | Type::Bytes | Type::Path | Type::PathBuf => Ty::Buf, + Type::U128 => Ty::U128, Type::Duration => Ty::Duration, Type::SocketAddr(_) => Ty::SocketAddr, Type::Waker => Ty::Waker, @@ -598,6 +606,7 @@ impl<'a> Abi<'a> { "contract", "buf", "buf_free", + "u128", "duration", "socket_addr", "waker", diff --git a/ffi/src/c.rs b/ffi/src/c.rs index 447d0fb07..ba5f00197 100644 --- a/ffi/src/c.rs +++ b/ffi/src/c.rs @@ -75,6 +75,9 @@ impl Header<'_> { self.line(format!( "/* Bytes. Passed in, a view Rust reads during the call and never frees. Returned,\n * an owned buffer to free once with {p}_buf_free. A null ptr is None. */\ntypedef struct {p}_buf {{\n\tconst uint8_t *ptr;\n\tsize_t len;\n\tvoid *owner;\n}} {p}_buf;\n\nvoid {p}_buf_free({p}_buf buf);\n" )); + self.line(format!( + "/* A u128: lo holds its low 64 bits and hi its high 64. */\ntypedef struct {p}_u128 {{\n\tuint64_t lo;\n\tuint64_t hi;\n}} {p}_u128;\n" + )); self.line(format!( "typedef struct {p}_duration {{\n\tuint64_t secs;\n\tuint32_t nanos;\n}} {p}_duration;\n" )); @@ -464,6 +467,7 @@ pub fn decl(abi: &Abi<'_>, ty: &Ty, name: &str) -> String { Ty::F64 => space("double"), Ty::Char => space("uint32_t"), Ty::Buf => space(&abi.rt("buf")), + Ty::U128 => space(&abi.rt("u128")), Ty::Duration => space(&abi.rt("duration")), Ty::SocketAddr => space(&abi.rt("socket_addr")), Ty::Struct(n) | Ty::Enum(n) => space(n), diff --git a/ffi/src/collect.rs b/ffi/src/collect.rs index d82e18f25..605fe8ad4 100644 --- a/ffi/src/collect.rs +++ b/ffi/src/collect.rs @@ -138,7 +138,7 @@ struct Site<'tcx> { key: Key<'tcx>, /// Its generic arguments as exported, each lifetime `'static`. args: ty::GenericArgsRef<'tcx>, - /// How many of its type arguments it is named by: each up to the last that is not its + /// How many of its type and const arguments it is named by: each up to the last that is not its /// default, as Rust spells it. explicit: usize, /// Its item's path, which names its members in the IR. @@ -154,8 +154,9 @@ struct Instantiation<'tcx> { /// The type parameters that stay parameters, each taking a lent closure or a foreign /// implementation, which typing maps. kept: Vec>, - /// The arguments of the type parameters that export at several types, which name it. - named: Vec>, + /// The arguments of the type parameters that export at several types, and of the const + /// parameters, which name it. + named: Vec>, } struct Collector<'tcx> { @@ -556,12 +557,12 @@ impl<'tcx> Collector<'tcx> { .skip_norm_wip() } - /// How many of `def_id`'s type arguments `args` names it by, as Rust spells it: each up - /// to the last that is not its default. + /// How many of `def_id`'s type and const arguments `args` names it by, as Rust spells + /// it: each up to the last that is not its default. fn explicit(&self, def_id: DefId, args: ty::GenericArgsRef<'tcx>) -> usize { let tcx = self.tcx; let params: Vec<&ty::GenericParamDef> = (tcx.generics_of(def_id).own_params.iter()) - .filter(|p| matches!(p.kind, ty::GenericParamDefKind::Type { .. })) + .filter(|p| !matches!(p.kind, ty::GenericParamDefKind::Lifetime)) .collect(); let mut explicit = params.len(); while let Some(param) = explicit.checked_sub(1).map(|i| params[i]) { @@ -579,6 +580,40 @@ impl<'tcx> Collector<'tcx> { explicit } + /// The first `explicit` type and const arguments of `args`, which name an instance. + fn shown_args(args: ty::GenericArgsRef<'tcx>, explicit: usize) -> Vec> { + let named = args + .iter() + .filter(|arg| !matches!(arg.kind(), GenericArgKind::Lifetime(_))); + named.take(explicit).collect() + } + + /// A type or const argument as reports spell it. + fn spell_arg(&self, arg: ty::GenericArg<'tcx>) -> String { + match arg.kind() { + GenericArgKind::Type(ty) => self.spell(ty), + GenericArgKind::Const(ct) => self.const_arg(ct).map_or_else(|_| ct.to_string(), |c| c.to_string()), + GenericArgKind::Lifetime(region) => region.to_string(), + } + } + + /// A const argument's value, which must be a `bool` or an integer that is not 128 bits. + fn const_arg(&self, ct: ty::Const<'tcx>) -> Result { + let tcx = self.tcx; + let refused = || why("const argument", ct.to_string()); + let ty::ConstKind::Value(value) = ct.kind() else { + return Err(refused()); + }; + let scalar = value.try_to_leaf().ok_or_else(refused)?; + let bits = scalar.to_bits(scalar.size()); + match value.ty.kind() { + ty::Bool => Ok(Literal::Bool(bits != 0)), + _ => int(value.ty) + .map(|int| Literal::Int(int, signed(tcx, value.ty, bits))) + .ok_or_else(refused), + } + } + /// A type as reports spell it, each export's by its path in the IR, and an instance by /// the arguments that name it. fn spell(&self, ty: Ty<'tcx>) -> String { @@ -592,7 +627,9 @@ impl<'tcx> Collector<'tcx> { None => tcx.def_path_str(adt.did()), }; let explicit = self.explicit(adt.did(), args); - let args: Vec = args.types().take(explicit).map(|ty| self.spell(ty)).collect(); + let args: Vec = (Self::shown_args(args, explicit).into_iter()) + .map(|arg| self.spell_arg(arg)) + .collect(); match args.is_empty() { true => path, false => format!("{path}<{}>", args.join(", ")), @@ -633,7 +670,10 @@ impl<'tcx> Collector<'tcx> { .flat_map(|choice| { let options: Vec> = match param.kind { ty::GenericParamDefKind::Lifetime => vec![tcx.lifetimes.re_static.into()], - ty::GenericParamDefKind::Const { .. } => Vec::new(), + ty::GenericParamDefKind::Const { .. } => (param.default_value(tcx)) + .map(|default| default.instantiate(tcx, &choice[..]).skip_norm_wip()) + .into_iter() + .collect(), ty::GenericParamDefKind::Type { .. } => (param.default_value(tcx)) .map(|default| default.instantiate(tcx, &choice[..]).skip_norm_wip()) .into_iter() @@ -764,23 +804,32 @@ impl<'tcx> Collector<'tcx> { named } - /// The first type argument an instance is named by that cannot map as [`Self::arg`] - /// requires, which is `()`, a primitive, `String`, `Bytes`, `Duration`, a foreign object, - /// or an export's type. - fn unarguable(&self, site: &Site<'tcx>) -> Option> { + /// Why the first argument an instance is named by cannot map as [`Self::arg`] requires: + /// a type that is not `()`, a primitive, `String`, `Bytes`, `Duration`, a foreign + /// object, or an export's type, or a const that is not a `bool` or integer. + fn unarguable(&self, site: &Site<'tcx>) -> Option { let tcx = self.tcx; - site.args.types().take(site.explicit).find(|&ty| match ty.kind() { - ty::Tuple(list) => !list.is_empty(), - ty::Bool | ty::Char | ty::Float(_) => false, - ty::Int(_) | ty::Uint(_) => int(ty).is_none(), - ty::Adt(adt, _) => { - tcx.lang_items().string() != Some(adt.did()) - && self.bytes != Some(adt.did()) - && !tcx.is_diagnostic_item(rustc_span::Symbol::intern("Duration"), adt.did()) - && !self.exported(adt.did().krate) - && !self.foreign.iter().any(|&(ty, _)| ty == adt.did()) - } - _ => true, + let mut args = Self::shown_args(site.args, site.explicit).into_iter(); + args.find_map(|arg| { + let ty = match arg.kind() { + GenericArgKind::Const(ct) => return self.const_arg(ct).err(), + GenericArgKind::Type(ty) => ty, + GenericArgKind::Lifetime(_) => return None, + }; + let unarguable = match ty.kind() { + ty::Tuple(list) => !list.is_empty(), + ty::Bool | ty::Char | ty::Float(_) => false, + ty::Int(_) | ty::Uint(_) => int(ty).is_none(), + ty::Adt(adt, _) => { + tcx.lang_items().string() != Some(adt.did()) + && self.bytes != Some(adt.did()) + && !tcx.is_diagnostic_item(rustc_span::Symbol::intern("Duration"), adt.did()) + && !self.exported(adt.did().krate) + && !self.foreign.iter().any(|&(ty, _)| ty == adt.did()) + } + _ => true, + }; + unarguable.then(|| why("type argument", self.spell(ty))) }) } @@ -809,7 +858,12 @@ impl<'tcx> Collector<'tcx> { fn shown(&mut self, shown: &Path, instance: &Instantiation<'tcx>) -> Option { let mut shown = shown.clone(); if !instance.named.is_empty() { - let spelled: Vec = instance.named.iter().map(|&ty| self.spell(ty.peel_refs())).collect(); + let spelled: Vec = (instance.named.iter()) + .map(|&arg| match arg.kind() { + GenericArgKind::Type(ty) => self.spell(ty.peel_refs()), + _ => self.spell_arg(arg), + }) + .collect(); *shown.last_mut().expect("a path has a segment") += &format!("<{}>", spelled.join(", ")); if self.exclude(&shown) { return None; @@ -923,10 +977,6 @@ impl<'tcx> Collector<'tcx> { Err(why("instance holding an instance of a type inside it", spelled)), )); } - // The IR keeps only an instance's type arguments, which would lose a const one. - if args.consts().next().is_some() { - return Some((key, Err(why("const generic", spelled)))); - } let (infcx, env) = tcx.infer_ctxt().build_with_typing_env(TypingEnv::fully_monomorphized()); for clause in tcx.clauses_of(def_id).instantiate(tcx, args).clauses { let clause = clause.skip_norm_wip(); @@ -938,7 +988,9 @@ impl<'tcx> Collector<'tcx> { let explicit = self.explicit(def_id, args); let mut shown = path.clone(); if explicit > 0 { - let spelled: Vec = args.types().take(explicit).map(|ty| self.spell(ty)).collect(); + let spelled: Vec = (Self::shown_args(args, explicit).into_iter()) + .map(|arg| self.spell_arg(arg)) + .collect(); let last = shown.last_mut().expect("a path has a segment"); *last = format!("{last}<{}>", spelled.join(", ")); } @@ -1074,7 +1126,7 @@ impl<'tcx> Collector<'tcx> { } for site in self.sites(def_id, &path) { let decl = match self.unarguable(&site) { - Some(ty) => Decl::Refused(why("type argument", self.spell(ty))), + Some(why) => Decl::Refused(why), None => self.declare_site(&site), }; self.decls.insert(site.key, decl); @@ -1255,6 +1307,7 @@ impl<'tcx> Collector<'tcx> { ty: Type::Ref(Mutability::Not, Box::new(Type::Str)), interned: false, }], + units: Vec::new(), ok: ty.clone(), err: Some(Type::String), future: false, @@ -1290,6 +1343,7 @@ impl<'tcx> Collector<'tcx> { docs: "The document as JSON text.".to_string(), receiver: Some(Receiver::Ref), params: Vec::new(), + units: Vec::new(), ok: Type::String, err: None, future: false, @@ -1359,18 +1413,22 @@ impl<'tcx> Collector<'tcx> { false => self.object(site), } } - DefKind::Enum if tcx.adt_def(def_id).variants().is_empty() => { - Decl::Refused(why("enum without variants", "")) - } + // No value is one, so it is a handle C never holds, which no C enum is. + DefKind::Enum if tcx.adt_def(def_id).variants().is_empty() => self.object(site), DefKind::Enum => Decl::Enum(0), _ => Decl::Callback(0), } } - /// The type arguments an instance is named by, each of which must export as a value or + /// The arguments an instance is named by, each type of which must export as a value or /// a handle: Rust fills in the defaults after them, which may be any type. fn args(&self, site: &Site<'tcx>) -> Result, Why> { - site.args.types().take(site.explicit).map(|ty| self.arg(ty)).collect() + (Self::shown_args(site.args, site.explicit).into_iter()) + .map(|arg| match arg.kind() { + GenericArgKind::Const(ct) => self.const_arg(ct).map(Type::Const), + _ => self.arg(arg.expect_ty()), + }) + .collect() } /// A type argument that names an instance: a value or a handle. @@ -1809,6 +1867,7 @@ impl<'tcx> Collector<'tcx> { docs: docs(tcx, field.did), receiver: Some(receiver), params, + units: Vec::new(), ok, err: None, future: false, @@ -2112,29 +2171,42 @@ impl<'tcx> Collector<'tcx> { *self.impls.borrow_mut() = impls; // What the glue names a declared type parameter as, which a call cannot always // infer; an `impl Trait` one cannot be named, and is inferred from its argument. - let mut arg = |ty: Ty<'tcx>| { - let mapped = match *ty.kind() { - ty::Ref(_, inner, ty::Mutability::Not) => { - self.arg(inner).map(|inner| Type::Ref(Mutability::Not, Box::new(inner))) - } - _ => self.arg(ty), + let mut arg = |arg: ty::GenericArg<'tcx>| { + let mapped = match arg.kind() { + GenericArgKind::Const(ct) => self.const_arg(ct).map(Type::Const), + GenericArgKind::Type(ty) => match *ty.kind() { + ty::Ref(_, inner, ty::Mutability::Not) => { + self.arg(inner).map(|inner| Type::Ref(Mutability::Not, Box::new(inner))) + } + _ => self.arg(ty), + }, + GenericArgKind::Lifetime(_) => unreachable!("no lifetime names an instance"), }; mapped.map_err(|why| errs.push(why)).ok() }; let instance = match instance.named.is_empty() { true => None, false => { - let declared = (tcx.generics_of(def_id).own_params.iter()) - .filter(|p| matches!(p.kind, ty::GenericParamDefKind::Type { synthetic: false, .. })); + let declared = (tcx.generics_of(def_id).own_params.iter()).filter(|p| { + matches!( + p.kind, + ty::GenericParamDefKind::Type { synthetic: false, .. } | ty::GenericParamDefKind::Const { .. } + ) + }); // A kept parameter is left for Rust to infer from its argument. let args = declared - .map(|p| args.type_at(p.index as usize)) - .filter_map(|ty| match instance.kept.contains(&ty) { + .map(|p| args[p.index as usize]) + .filter_map(|a| match a.as_type().is_some_and(|ty| instance.kept.contains(&ty)) { true => Some(None), - false => arg(ty).map(Some), + false => arg(a).map(Some), + }) + .collect(); + let named = (instance.named.iter()) + .filter_map(|&a| match a.kind() { + GenericArgKind::Type(ty) => arg(ty.peel_refs().into()), + _ => arg(a), }) .collect(); - let named = instance.named.iter().filter_map(|&ty| arg(ty.peel_refs())).collect(); Some(crate::ir::Instance { args, named }) } }; @@ -2230,7 +2302,14 @@ impl<'tcx> Collector<'tcx> { } let mut tasks = Vec::new(); let mut params = Vec::new(); + let mut units = Vec::new(); for (i, ty) in inputs { + // C passes nothing for `()`, the one value of its type. A callback's stays + // refused: the glue's implementation of its trait names every parameter. + if ty.is_unit() && !callback { + units.push(i - usize::from(has_self)); + continue; + } let name = names .get(i) .copied() @@ -2389,6 +2468,7 @@ impl<'tcx> Collector<'tcx> { docs: docs(tcx, def_id), receiver, params, + units, ok, err, future, @@ -2427,14 +2507,29 @@ impl<'tcx> Collector<'tcx> { } let base = args.clone(); let mut kept = Vec::new(); - // Each choice of the parameters so far, with the types that name it. - let mut choices = vec![(args, Vec::new())]; + // Each choice of the parameters so far, with the arguments that name it. + let mut choices: Vec<(Vec>, Vec>)> = vec![(args, Vec::new())]; for (i, arg) in identity.iter().enumerate() { let param = match arg.kind() { _ if i < count => continue, GenericArgKind::Lifetime(_) => continue, - // The first refusal ends it, before any later parameter's candidates are mapped. - GenericArgKind::Const(_) => return Err(vec![why("const generic", "")]), + GenericArgKind::Const(param) => { + let next: Vec<_> = (choices.into_iter()) + .flat_map(|(args, named)| { + (self.each_const(def_id, param, &args).into_iter()).map(move |value| { + let mut args = args.clone(); + args[i] = value.into(); + (args, [&named[..], &[value.into()]].concat()) + }) + }) + .collect(); + // The first refusal ends it, before any later parameter's candidates are mapped. + if next.is_empty() { + return Err(vec![why("const generic", param.to_string())]); + } + choices = next; + continue; + } GenericArgKind::Type(param) if self.lent(def_id, param) => { kept.push(param); continue; @@ -2490,7 +2585,7 @@ impl<'tcx> Collector<'tcx> { for ty in each { let mut args = args.clone(); args[i] = ty.into(); - next.push((args, [&named[..], &[ty]].concat())); + next.push((args, [&named[..], &[ty.into()]].concat())); } } // A choice no type of this parameter meets is dropped; with none left, the first @@ -2934,16 +3029,19 @@ impl<'tcx> Collector<'tcx> { let Some(args) = at(candidate) else { continue; }; - let named = spelled(args).into_iter().filter_map(exported).all(|(adt, args)| { - let key = self.key(adt, args); - match self.inferring { - true => self.instances.iter().any(|site| site.key == key), - false => matches!( - self.decls.get(&key), - Some(Decl::Record(_) | Decl::Enum(_) | Decl::Object(_)) - ), - } - }); + // A type naming a parameter not yet chosen is checked once it is. + let named = (spelled(args).into_iter().filter_map(exported)) + .filter(|(_, args)| !args.has_non_region_param()) + .all(|(adt, args)| { + let key = self.key(adt, args); + match self.inferring { + true => self.instances.iter().any(|site| site.key == key), + false => matches!( + self.decls.get(&key), + Some(Decl::Record(_) | Decl::Enum(_) | Decl::Object(_)) + ), + } + }); if named { found.push(candidate); } @@ -2952,6 +3050,56 @@ impl<'tcx> Collector<'tcx> { found } + /// The values a const parameter exports at: as an argument of a generic type the + /// signature names, each value an instance of it has there, where each generic type the + /// signature then names at chosen arguments only is an instance. + fn each_const(&self, def_id: DefId, param: ty::Const<'tcx>, args: &[ty::GenericArg<'tcx>]) -> Vec> { + let tcx = self.tcx; + let exported = |ty: Ty<'tcx>| match ty.kind() { + ty::Adt(adt, args) if self.exported(adt.did().krate) && self.typed(adt.did()) => Some((adt.did(), args)), + _ => None, + }; + let spelled = |args| self.spelled(def_id, args); + let ty::ConstKind::Param(p) = param.kind() else { + unreachable!("a generic const argument is a parameter"); + }; + let mut found: Vec> = Vec::new(); + for (adt, at) in spelled(tcx.mk_args(args)).into_iter().filter_map(exported) { + let Some(j) = at.iter().position(|arg| arg == param.into()) else { + continue; + }; + for site in self.instances.iter().filter(|site| site.key.def_id == adt) { + let Some(value) = site.args[j].as_const() else { + continue; + }; + if found.contains(&value) { + continue; + } + let mut args = args.to_vec(); + args[p.index as usize] = value.into(); + // A type naming a parameter not yet chosen is checked once it is. + let named = spelled(tcx.mk_args(&args)) + .into_iter() + .filter_map(exported) + .filter(|(_, args)| !args.has_non_region_param()) + .all(|(adt, args)| { + let key = self.key(adt, args); + match self.inferring { + true => self.instances.iter().any(|site| site.key == key), + false => matches!( + self.decls.get(&key), + Some(Decl::Record(_) | Decl::Enum(_) | Decl::Object(_)) + ), + } + }); + if named { + found.push(value); + } + } + } + found + } + /// Whether an `Fn` trait bounds a type parameter, which then takes a lent closure. fn lent(&self, def_id: DefId, param: Ty<'tcx>) -> bool { let tcx = self.tcx; @@ -3681,6 +3829,7 @@ impl<'tcx> Collector<'tcx> { docs: docs(tcx, def_id), receiver: None, params: Vec::new(), + units: Vec::new(), ok, err: None, future: false, @@ -3736,6 +3885,7 @@ impl<'tcx> Collector<'tcx> { ty::Char => Type::Char, ty::Float(ty::FloatTy::F32) => Type::F32, ty::Float(ty::FloatTy::F64) => Type::F64, + ty::Uint(ty::UintTy::U128) => Type::U128, ty::Int(_) | ty::Uint(_) => Type::Int(int(ty).ok_or_else(|| why("128-bit integer", ty.to_string()))?), ty::Ref(_, inner, m) => { let inner_ty = match inner.kind() { @@ -4300,8 +4450,21 @@ fn matches<'tcx>(tcx: TyCtxt<'tcx>, pattern: Ty<'tcx>, ty: Ty<'tcx>, args: &mut } } (ty::Adt(a, pattern_args), ty::Adt(b, ty_args)) if a.did() == b.did() => { - (pattern_args.iter().zip(ty_args.iter())).all(|(p, t)| match (p.as_type(), t.as_type()) { - (Some(p), Some(t)) => matches(tcx, p, t, args), + (pattern_args.iter().zip(ty_args.iter())).all(|(p, t)| match (p.kind(), t.kind()) { + (GenericArgKind::Type(p), GenericArgKind::Type(t)) => matches(tcx, p, t, args), + (GenericArgKind::Const(p), GenericArgKind::Const(t)) => match p.kind() { + ty::ConstKind::Param(param) => { + let slot = &mut args[param.index as usize]; + match slot.as_const() == Some(p) { + true => { + *slot = t.into(); + true + } + false => slot.as_const() == Some(t), + } + } + _ => p == t, + }, _ => true, }) } diff --git a/ffi/src/cpp.rs b/ffi/src/cpp.rs index 6b3132f2b..496bc7017 100644 --- a/ffi/src/cpp.rs +++ b/ffi/src/cpp.rs @@ -100,6 +100,7 @@ impl<'a> Header<'a> { fn owned(&self, ty: &Type) -> String { let abi = self.abi; match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => "void".to_string(), Type::Bool => "bool".to_string(), Type::Int(int) => cpp_int(*int).to_string(), @@ -109,6 +110,7 @@ impl<'a> Header<'a> { Type::Str | Type::String | Type::Slice | Type::Vec | Type::Bytes | Type::Path | Type::PathBuf => { format!("::{}::buf", self.ns) } + Type::U128 => format!("::{}::u128", self.ns), Type::Duration => format!("::{}::duration", self.ns), Type::SocketAddr(_) => format!("::{}::socket_addr", self.ns), Type::List(_, inner) => format!("::std::vector<{}>", self.owned(inner)), @@ -247,10 +249,13 @@ impl<'a> Header<'a> { return format!("{d}::access::raw(::{ns}::view({e}))"); } match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => unreachable!("no value is unit"), Type::Bool | Type::Int(_) | Type::F32 | Type::F64 => e.to_string(), Type::Char => format!("static_cast({e})"), - Type::Duration | Type::SocketAddr(_) | Type::Range(_) | Type::Bound(_) | Type::Bounds(_) => e.to_string(), + Type::U128 | Type::Duration | Type::SocketAddr(_) | Type::Range(_) | Type::Bound(_) | Type::Bounds(_) => { + e.to_string() + } Type::Waker => format!("{d}::give({d}::access::raw({e}))"), Type::List(_, inner) => { let x = format!("x{}_", lists(inner)); @@ -311,7 +316,9 @@ impl<'a> Header<'a> { match ty { Type::Bool | Type::Int(_) | Type::F32 | Type::F64 => e.to_string(), Type::Char => format!("static_cast({e})"), - Type::Duration | Type::SocketAddr(_) | Type::Range(_) | Type::Bound(_) | Type::Bounds(_) => e.to_string(), + Type::U128 | Type::Duration | Type::SocketAddr(_) | Type::Range(_) | Type::Bound(_) | Type::Bounds(_) => { + e.to_string() + } Type::List(_, inner) => { let c = format!("c{}_", lists(inner)); format!( @@ -426,6 +433,7 @@ impl<'a> Header<'a> { upper = p.to_uppercase() )); let mut aliases = vec![ + ("u128".to_string(), format!("{p}_u128")), ("duration".to_string(), format!("{p}_duration")), ("socket_addr".to_string(), format!("{p}_socket_addr")), ]; diff --git a/ffi/src/dart.rs b/ffi/src/dart.rs index 75d41c36d..e4c87a357 100644 --- a/ffi/src/dart.rs +++ b/ffi/src/dart.rs @@ -219,6 +219,7 @@ pub fn generate(abi: &Abi<'_>, packages: &str, contract: u64) -> Result Gen<'a> { fn dt(&mut self, ty: &Type) -> String { let abi = self.abi; match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => "void".to_string(), Type::Bool => "bool".to_string(), Type::Int(_) => "int".to_string(), Type::F32 | Type::F64 => "double".to_string(), Type::Char | Type::Str | Type::String | Type::Path | Type::PathBuf => "String".to_string(), Type::Slice | Type::Vec | Type::Bytes => "typed.Uint8List".to_string(), + Type::U128 => "BigInt".to_string(), Type::Duration => "Duration".to_string(), Type::SocketAddr(_) => "SocketAddr".to_string(), Type::List(_, inner) => format!("List<{}>", self.dt(inner)), @@ -493,7 +496,10 @@ impl<'a> Gen<'a> { /// Whether `dart:ffi` passes a C type as a Dart scalar, not a struct. fn scalar(ty: &Ty) -> bool { - !matches!(ty, Ty::Buf | Ty::Duration | Ty::SocketAddr | Ty::Struct(_) | Ty::Waker) + !matches!( + ty, + Ty::Buf | Ty::U128 | Ty::Duration | Ty::SocketAddr | Ty::Struct(_) | Ty::Waker + ) } /// The class of a C struct, defined once. @@ -501,6 +507,7 @@ impl<'a> Gen<'a> { let abi = self.abi; let name = match ty { Ty::Buf => return "_Buf".to_string(), + Ty::U128 => return "_U128".to_string(), Ty::Duration => return "_Duration".to_string(), Ty::SocketAddr => return "_Addr".to_string(), Ty::Waker => return "_Waker".to_string(), @@ -742,6 +749,7 @@ impl<'a> Gen<'a> { match ty.base() { Type::Str | Type::String | Type::Path | Type::PathBuf => format!("a_.string({dst}, {e});"), Type::Slice | Type::Vec | Type::Bytes => format!("a_.bytes({dst}, {e});"), + Type::U128 => format!("_n.u128({dst}, {e});"), Type::Duration => format!("_n.duration({dst}, {e});"), Type::SocketAddr(family) => { let family = match family { @@ -1015,6 +1023,7 @@ impl<'a> Gen<'a> { Type::Slice | Type::Vec | Type::Bytes => format!("_n.bytes({e})"), Type::Bool | Type::F32 | Type::F64 | Type::Int(_) => e.to_string(), Type::Char => format!("_n.scalar({e})"), + Type::U128 => format!("_n.u128Of({e})"), Type::Duration => format!("_n.durationOf({e})"), Type::SocketAddr(_) => format!("_n.addrOf({e})"), Type::Option(inner) => match inner.base() { diff --git a/ffi/src/glue.rs b/ffi/src/glue.rs index b46b39c30..9c526a9ac 100644 --- a/ffi/src/glue.rs +++ b/ffi/src/glue.rs @@ -10,7 +10,7 @@ use std::path::Path; use crate::abi::{self, Abi, Ty}; use crate::config::Package; -use crate::ir::{Access, Collection, Family, Function, Mutability, Receiver, Task, TaskKind, Traits, Type}; +use crate::ir::{Access, Collection, Family, Function, Literal, Mutability, Receiver, Task, TaskKind, Traits, Type}; const RUNTIME: &str = include_str!("../runtime/rt.rs"); @@ -64,6 +64,7 @@ pub fn rs(ty: &Ty) -> String { Ty::F64 => "f64".to_string(), Ty::Char => "u32".to_string(), Ty::Buf => "rt::Buf".to_string(), + Ty::U128 => "rt::U128".to_string(), Ty::Duration => "rt::Duration".to_string(), Ty::SocketAddr => "rt::SocketAddr".to_string(), Ty::Struct(name) => name.clone(), @@ -343,6 +344,7 @@ impl Gen<'_> { Type::Unit => "()".to_string(), Type::Bool => "bool".to_string(), Type::Int(int) => int.rust().to_string(), + Type::U128 => "u128".to_string(), Type::F32 => "f32".to_string(), Type::F64 => "f64".to_string(), Type::Char => "char".to_string(), @@ -375,6 +377,9 @@ impl Gen<'_> { format!("::{}", foreign(&path[0])) + &path[1..].iter().map(|s| format!("::{s}")).collect::() } Type::Object(i) => generic(&api.objects[*i].path, api.objects[*i].lifetimes, &api.objects[*i].args), + // A negative argument is an expression, which Rust takes only in braces. + Type::Const(Literal::Int(_, v)) if *v < 0 => format!("{{{v}}}"), + Type::Const(value) => value.to_string(), other => unreachable!("no callback or type argument is {other:?}"), } } @@ -382,6 +387,7 @@ impl Gen<'_> { /// The Rust value of type `ty` that the C value `e` holds, copied out of it. fn lift(&self, ty: &Type, e: &str) -> String { match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => e.to_string(), Type::Bool | Type::Int(_) | Type::F32 | Type::F64 => e.to_string(), Type::Char => format!("rt::char({e}, w)"), @@ -389,6 +395,7 @@ impl Gen<'_> { Type::Slice | Type::Vec => format!("unsafe {{ {e}.bytes(w) }}.to_vec()"), Type::Bytes => format!("::bytes::Bytes::copy_from_slice(unsafe {{ {e}.bytes(w) }})"), Type::Path | Type::PathBuf => format!("unsafe {{ {e}.path(w) }}.to_path_buf()"), + Type::U128 => format!("{e}.lift()"), Type::Duration | Type::SocketAddr(Family::Any) => format!("{e}.lift(w)"), Type::SocketAddr(Family::V4) => format!("{e}.lift_v4(w)"), Type::SocketAddr(Family::V6) => format!("{e}.lift_v6(w)"), @@ -491,6 +498,7 @@ impl Gen<'_> { /// The C value of the Rust value `e` of type `ty`, which the caller frees. fn lower(&self, ty: &Type, e: &str) -> String { match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => "()".to_string(), Type::Bool | Type::Int(_) | Type::F32 | Type::F64 => e.to_string(), Type::Char => format!("{e} as u32"), @@ -501,6 +509,7 @@ impl Gen<'_> { Type::Bytes => format!("rt::Buf::owned({e})"), Type::Path => format!("rt::Buf::path_buf({e}.to_path_buf())"), Type::PathBuf => format!("rt::Buf::path_buf({e})"), + Type::U128 => format!("rt::U128::lower({e})"), Type::Duration => format!("rt::Duration::lower({e})"), Type::SocketAddr(_) => format!("rt::SocketAddr::lower(::std::net::SocketAddr::from({e}))"), Type::List(_, inner) => format!( @@ -1175,7 +1184,7 @@ impl Gen<'_> { /// The statements that hand a call's `result` to C: return it, or for a fallible /// function or a tuple, write it through its out pointers or `err`, and return whether /// it succeeded. - fn finish(&self, function: &Function, ret: (&str, &str, &str)) -> String { + fn finish(&self, function: &Function, ok: &Type, ret: (&str, &str, &str)) -> String { let (ok_value, ready, error) = ret; let write = |ty: &Type, out: &str, v: &str| { format!( @@ -1184,9 +1193,10 @@ impl Gen<'_> { self.lower(ty, v) ) }; - let write_ok = match &function.ok { - Type::Unit => String::new(), - Type::Tuple(elements) => { + // The outs C passes follow what the function returns, which `ok` may own. + let write_ok = match (&function.ok, ok) { + (_, Type::Unit) => String::new(), + (Type::Tuple(_), Type::Tuple(elements)) => { let names: Vec = (0..elements.len()).map(|n| format!("v{n}")).collect(); let writes: Vec = elements .iter() @@ -1197,15 +1207,15 @@ impl Gen<'_> { // The trailing comma makes `(v0,)` a tuple pattern, not parentheses. format!("let ({},) = v;\n\t{}", names.join(", "), writes.join("\n\t")) } - ok => write(ok, "out", "v"), + (_, ok) => write(ok, "out", "v"), }; match &function.err { - None if ok_value.is_empty() => match function.ok { - Type::Unit => "result".to_string(), - Type::Tuple(_) => format!("let v = result;\n\t{write_ok}"), - _ => self.lower(&function.ok, "result"), + None if ok_value.is_empty() => match (&function.ok, ok) { + (_, Type::Unit) => "result".to_string(), + (Type::Tuple(_), _) => format!("let v = result;\n\t{write_ok}"), + _ => self.lower(ok, "result"), }, - None => match function.ok { + None => match ok { Type::Unit => format!("let _ = result;\n\t{ready}"), _ => format!("let v = result;\n\t{write_ok}\n\t{ready}"), }, @@ -1255,7 +1265,15 @@ impl Gen<'_> { } } } + // Each parameter's place among Rust's after `self`, past the `()` ones C does not + // pass and a future's context, which is the poll's. + let omitted = |at: &usize| { + function.units.contains(at) + || (function.future && function.task.as_ref().is_some_and(|task| task.at == *at)) + }; + let mut places = (0..).filter(|at| !omitted(at)); for (n, param) in function.params.iter().enumerate() { + let place = places.next().expect("a place for each parameter"); let (_, c_ty) = c_params.next().expect("a C parameter for each"); params.push(format!("p{n}: {}", rs(c_ty))); if let Type::Closure(..) = param.ty { @@ -1267,7 +1285,7 @@ impl Gen<'_> { Some(Task { at, kind: TaskKind::Context(m), - }) if *at == n && !function.future => { + }) if *at == place && !function.future => { let (binding, pass) = match m { Mutability::Mut => ("let mut", format!("&mut a{n}")), Mutability::Not => ("let", format!("&a{n}")), @@ -1292,6 +1310,9 @@ impl Gen<'_> { // context, or a value made from its waker and kept until the next poll, so what that // poll registers stays registered. let mut made = String::new(); + // Each argument C does not pass, by its place among Rust's parameters after `self`, + // inserted in order so each lands where Rust takes it. + let mut extra: Vec<(usize, String)> = function.units.iter().map(|&at| (at, "()".to_string())).collect(); if let Some(task) = function.task.as_ref().filter(|_| function.future) { let pass = match &task.kind { TaskKind::Context(Mutability::Mut) => "&mut *t_cx".to_string(), @@ -1304,7 +1325,11 @@ impl Gen<'_> { "&*t_made".to_string() } }; - args.insert(usize::from(receiver_ty.is_some()) + task.at, pass); + extra.push((task.at, pass)); + } + extra.sort_by_key(|(at, _)| *at); + for (at, arg) in extra { + args.insert(usize::from(receiver_ty.is_some()) + at, arg); } // Rust writes back what the call left in each value C passed for `&mut`, on every // path, errors included. @@ -1377,15 +1402,23 @@ impl Gen<'_> { ) } }; + // The value C takes is lowered when it polls, outside the future, so a result + // borrowing what the future holds is cloned before the future ends. + let (ok, own) = owned(&function.ok); + let later = match (own, &function.err) { + (None, _) => format!("{later}.await"), + (Some(own), Some(_)) => format!("{later}.await.map(|v| {own})"), + (Some(own), None) => format!("{{ let v = {later}.await; {own} }}"), + }; let bind: String = Self::outs(function) .iter() .enumerate() .map(|(n, out)| format!("let {out} = outs[{n}];\n\t\t\t")) .collect(); self.line(format!( - "#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn {name}({}) -> *mut rt::Future {{\n\tlet w = \"{name}\";\n\tlet _ = w;\n\t{body}{now}\n\trt::Future::new(async move {{\n\t\tlet result = {later}.await;\n\t\t::std::boxed::Box::new(move |outs: &[*mut ::std::ffi::c_void], err: *mut ::std::ffi::c_void| -> i32 {{\n\t\t\tlet _ = (outs, err);\n\t\t\t{bind}{}\n\t\t}}) as rt::Done\n\t}})\n}}\n", + "#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn {name}({}) -> *mut rt::Future {{\n\tlet w = \"{name}\";\n\tlet _ = w;\n\t{body}{now}\n\trt::Future::new(async move {{\n\t\tlet result = {later};\n\t\t::std::boxed::Box::new(move |outs: &[*mut ::std::ffi::c_void], err: *mut ::std::ffi::c_void| -> i32 {{\n\t\t\tlet _ = (outs, err);\n\t\t\t{bind}{}\n\t\t}}) as rt::Done\n\t}})\n}}\n", params.join(", "), - self.finish(function, ("v", "rt::READY", "rt::ERROR")) + self.finish(function, &ok, ("v", "rt::READY", "rt::ERROR")) )); let poll_params: Vec = poll.params[2..] .iter() @@ -1417,8 +1450,8 @@ impl Gen<'_> { let ret = rs(&export.call.ret); self.lends = function.lends; let finish = match function.err { - Some(_) => self.finish(function, ("v", "true", "false")), - None => self.finish(function, ("", "", "")), + Some(_) => self.finish(function, &function.ok, ("v", "true", "false")), + None => self.finish(function, &function.ok, ("", "", "")), }; self.lends = false; self.line(format!( @@ -1432,3 +1465,35 @@ impl Gen<'_> { )); } } + +/// What a function returns, owned: each reference at its top or in a returned tuple as +/// its clone's type, with the expression cloning `v` into it, if any is one. +fn owned(ok: &Type) -> (Type, Option) { + let one = |ty: &Type| match ty { + Type::Ref(_, inner) => Some(match &**inner { + Type::Str => Type::String, + Type::Slice => Type::Vec, + Type::Path => Type::PathBuf, + inner => inner.clone(), + }), + _ => None, + }; + match ok { + Type::Tuple(elements) if elements.iter().any(|e| one(e).is_some()) => { + let names: Vec = (0..elements.len()).map(|n| format!("v{n}")).collect(); + let owned: Vec = (elements.iter().zip(&names)) + .map(|(e, n)| match one(e) { + Some(_) => format!("::std::borrow::ToOwned::to_owned({n})"), + None => n.clone(), + }) + .collect(); + let ty = Type::Tuple(elements.iter().map(|e| one(e).unwrap_or_else(|| e.clone())).collect()); + let own = format!("{{ let ({},) = v; ({},) }}", names.join(", "), owned.join(", ")); + (ty, Some(own)) + } + ok => match one(ok) { + Some(ty) => (ty, Some("::std::borrow::ToOwned::to_owned(v)".to_string())), + None => (ok.clone(), None), + }, + } +} diff --git a/ffi/src/go.rs b/ffi/src/go.rs index ea42df715..80ca9bb10 100644 --- a/ffi/src/go.rs +++ b/ffi/src/go.rs @@ -44,6 +44,7 @@ const CONVERSIONS: &str = include_str!("../runtime/ffi_cgo.go"); /// The standard library's packages the generated items name. const STD: &[(&str, &str)] = &[ + ("big", "math/big"), ("context", "context"), ("cgo", "runtime/cgo"), ("netip", "net/netip"), @@ -411,9 +412,9 @@ impl<'a> Gen<'a> { "// Code generated by rust2-ffi. DO NOT EDIT.\n\n\ package {name}\n\n\ /*\n#cgo CFLAGS: -I${{SRCDIR}}/../../include\n#include \n#include \"{p}.h\"\n*/\nimport \"C\"\n\n\ - import (\n\t\"context\"\n\t\"math\"\n\t\"net\"\n\t\"net/netip\"\n\t\"runtime\"\n\t\"runtime/cgo\"\n\t\"strconv\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"{rt}\"\n)\n\n\ + import (\n\t\"context\"\n\t\"math\"\n\t\"math/big\"\n\t\"net\"\n\t\"net/netip\"\n\t\"runtime\"\n\t\"runtime/cgo\"\n\t\"strconv\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"{rt}\"\n)\n\n\ // The runtime's names for the export's C types and functions.\n\ - type (\n\tcBuf = C.{p}_buf\n\tcWaker = C.{p}_waker\n\tcDuration = C.{p}_duration\n\tcAddr = C.{p}_socket_addr\n)\n\n\ + type (\n\tcBuf = C.{p}_buf\n\tcWaker = C.{p}_waker\n\tcU128 = C.{p}_u128\n\tcDuration = C.{p}_duration\n\tcAddr = C.{p}_socket_addr\n)\n\n\ var (\n\tgoCall = (*[0]byte)(rt.GoCall_())\n\tgoRelease = (*[0]byte)(rt.GoRelease_())\n)\n\n\ func cBufFree(buf cBuf) {{\n\tC.{p}_buf_free(buf)\n}}\n\n" ); @@ -528,6 +529,7 @@ impl<'a> Gen<'a> { /// The Go type of `ty`, as a value, a parameter, or a result. fn go(&mut self, ty: &Type) -> String { match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => unreachable!("no value is unit"), Type::Bool => "bool".to_string(), Type::Int(int) => go_int(*int).to_string(), @@ -536,13 +538,14 @@ impl<'a> Gen<'a> { Type::Char => "rune".to_string(), Type::Str | Type::String | Type::Path | Type::PathBuf => "string".to_string(), Type::Slice | Type::Vec | Type::Bytes => self.qualify(Packages::RT, "Bytes"), + Type::U128 => "*big.Int".to_string(), Type::Duration => "time.Duration".to_string(), Type::SocketAddr(_) => "netip.AddrPort".to_string(), Type::List(_, inner) => format!("[]{}", self.go(inner)), Type::Waker => "func()".to_string(), Type::Option(inner) => match inner.base() { // Already a pointer, nil for none. - Type::Object(_) => self.go(inner), + Type::Object(_) | Type::U128 => self.go(inner), _ => format!("*{}", self.go(inner)), }, Type::Box(inner) | Type::Ref(_, inner) => self.go(inner), @@ -611,6 +614,7 @@ impl<'a> Gen<'a> { Ty::F64 => "C.double".to_string(), Ty::Char => "C.uint32_t".to_string(), Ty::Buf => "cBuf".to_string(), + Ty::U128 => "cU128".to_string(), Ty::Duration => "cDuration".to_string(), Ty::SocketAddr => "cAddr".to_string(), Ty::Struct(n) | Ty::Enum(n) => format!("C.{n}"), @@ -681,6 +685,7 @@ impl<'a> Gen<'a> { fn lower(&mut self, ty: &Type, e: &str) -> String { let abi = self.abi; match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Str | Type::String | Type::Path | Type::PathBuf => format!("a_.bufString({e})"), Type::Slice | Type::Vec | Type::Bytes => format!("a_.bufView({e})"), Type::Unit => unreachable!("no value is unit"), @@ -689,6 +694,7 @@ impl<'a> Gen<'a> { Type::F32 => format!("C.float({e})"), Type::F64 => format!("C.double({e})"), Type::Char => format!("C.uint32_t({e})"), + Type::U128 => format!("lowerU128_({e})"), Type::Duration => format!("lowerDuration_({e})"), Type::SocketAddr(Family::Any) => format!("lowerAddr_({e})"), Type::SocketAddr(Family::V4) => format!("lowerAddrV4_({e})"), @@ -727,11 +733,14 @@ impl<'a> Gen<'a> { let arena = self.arena(inner); let inner = (**inner).clone(); self.helper(&format!("lower_{c}"), |g| { - let go = g.go(&inner); - let value = g.lower(&inner, "*v"); + // A Go pointer already, which nil is none of. + let (go, value) = match inner.base() { + Type::U128 => (g.go(&inner), g.lower(&inner, "v")), + _ => (format!("*{}", g.go(&inner)), g.lower(&inner, "*v")), + }; let param = g.arena_param(&inner); format!( - "func lower_{c}({param}v *{go}) C.{c} {{\n\tif v == nil {{\n\t\treturn C.{c}{{}}\n\t}}\n\treturn C.{c}{{some: true, value: {value}}}\n}}\n\n" + "func lower_{c}({param}v {go}) C.{c} {{\n\tif v == nil {{\n\t\treturn C.{c}{{}}\n\t}}\n\treturn C.{c}{{some: true, value: {value}}}\n}}\n\n" ) }); match arena { @@ -816,6 +825,7 @@ impl<'a> Gen<'a> { Type::F32 => format!("float32({e})"), Type::F64 => format!("float64({e})"), Type::Char => format!("rune({e})"), + Type::U128 => format!("liftU128_({e})"), Type::Duration => format!("liftDuration_({e})"), Type::SocketAddr(_) => format!("liftAddr_({e})"), Type::Range(_) | Type::Bound(_) | Type::Bounds(_) => { @@ -850,10 +860,13 @@ impl<'a> Gen<'a> { let c = abi.option(inner); let inner = (**inner).clone(); self.helper(&format!("lift_{c}"), |g| { - let go = g.go(&inner); let value = g.lift(&inner, "c.value"); + let (go, v) = match inner.base() { + Type::U128 => (g.go(&inner), "v"), + _ => (format!("*{}", g.go(&inner)), "&v"), + }; format!( - "func lift_{c}(c C.{c}) *{go} {{\n\tif !c.some {{\n\t\treturn nil\n\t}}\n\tv := {value}\n\treturn &v\n}}\n\n" + "func lift_{c}(c C.{c}) {go} {{\n\tif !c.some {{\n\t\treturn nil\n\t}}\n\tv := {value}\n\treturn {v}\n}}\n\n" ) }); format!("lift_{c}({e})") @@ -2361,6 +2374,7 @@ fn local(name: &str) -> String { "strconv", "sync", "atomic", + "big", "time", "unsafe", "self", diff --git a/ffi/src/ir.rs b/ffi/src/ir.rs index 3f6983222..b519efe27 100644 --- a/ffi/src/ir.rs +++ b/ffi/src/ir.rs @@ -47,6 +47,7 @@ impl Api { Type::Record(i) => self.rust(&self.records[*i].path, &self.records[*i].args), Type::Enum(i) => self.rust(&self.enums[*i].path, &self.enums[*i].args), Type::Object(i) => self.rust(&self.objects[*i].path, &self.objects[*i].args), + Type::Const(value) => value.to_string(), other => unreachable!("no type argument is {other:?}"), } } @@ -76,7 +77,7 @@ pub struct Record { /// Its path with its instance's arguments, which the config names it by, such as /// `moq_mux::catalog::Producer`. pub shown: Path, - /// The type arguments of the instance it is, empty unless the type is generic. + /// The type and const arguments of the instance it is, empty unless the type is generic. pub args: Vec, pub span: Span, pub docs: String, @@ -187,6 +188,8 @@ pub struct Function { pub docs: String, pub receiver: Option, pub params: Vec, + /// Where among Rust's parameters each `()` one sits, which C does not pass. + pub units: Vec, /// What it returns, or for a future, what the future does. pub ok: Type, /// The error of a `Result` it returns: `String` for another crate's error, as its message. @@ -215,12 +218,12 @@ pub struct Function { pub home: Home, } -/// An instance of a generic function or method, at one of the types a type parameter -/// exports at. +/// An instance of a generic function or method, at one of the arguments a type or const +/// parameter exports at. pub struct Instance { - /// The arguments of its declared type parameters, which the call names, as Rust - /// cannot infer one no argument names; its argument infers an `impl Trait` one, and - /// one left `None`, which takes a lent closure or a foreign implementation. + /// The arguments of its declared type and const parameters, which the call names, as + /// Rust cannot infer one no argument names; its argument infers an `impl Trait` one, + /// and one left `None`, which takes a lent closure or a foreign implementation. pub args: Vec>, /// The arguments that tell it from its other instances, which name it. pub named: Vec, @@ -294,17 +297,30 @@ pub struct Constant { pub value: Literal, } +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum Literal { Bool(bool), Int(Int, i128), } +impl std::fmt::Display for Literal { + /// The value as Rust writes it. + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Literal::Bool(b) => write!(f, "{b}"), + Literal::Int(_, v) => write!(f, "{v}"), + } + } +} + /// A type as the API uses it, in the exact form the Rust side takes or returns. #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum Type { Unit, Bool, Int(Int), + /// `u128`, which C passes as its two `u64` halves. + U128, F32, F64, Char, @@ -351,6 +367,8 @@ pub enum Type { Bounds(Int), /// A tuple of one element or more. Tuple(Vec), + /// A const argument of an instance, such as the `8` of `Buffer<8>`. + Const(Literal), } /// The Rust collection a [`Type::List`] is, which Rust builds from its items in order. diff --git a/ffi/src/kotlin.rs b/ffi/src/kotlin.rs index 81e48fb78..a175f91e4 100644 --- a/ffi/src/kotlin.rs +++ b/ffi/src/kotlin.rs @@ -363,6 +363,7 @@ impl<'a> Gen<'a> { fn kt(&mut self, ty: &Type) -> String { let abi = self.abi; match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => "Unit".to_string(), Type::Bool => "Boolean".to_string(), Type::Int(int) => kt_int(*int).to_string(), @@ -372,6 +373,7 @@ impl<'a> Gen<'a> { Type::Str | Type::String => "String".to_string(), Type::Slice | Type::Vec | Type::Bytes => "Bytes".to_string(), Type::Path | Type::PathBuf => "String".to_string(), + Type::U128 => "java.math.BigInteger".to_string(), Type::Duration => "kotlin.time.Duration".to_string(), Type::SocketAddr(_) => "java.net.InetSocketAddress".to_string(), Type::List(_, inner) => format!("kotlin.collections.List<{}>", self.kt(inner)), @@ -425,6 +427,7 @@ impl<'a> Gen<'a> { Type::Char => format!("{w}.i32({e})"), Type::Str | Type::String | Type::Path | Type::PathBuf => format!("{w}.string({e})"), Type::Slice | Type::Vec | Type::Bytes => format!("{w}.bytes({e})"), + Type::U128 => format!("{w}.u128({e})"), Type::Duration => format!("{w}.duration({e})"), Type::SocketAddr(Family::Any) => format!("{w}.socketAddr({e})"), Type::SocketAddr(Family::V4) => format!("{w}.socketAddrV4({e})"), @@ -491,6 +494,7 @@ impl<'a> Gen<'a> { Type::Path | Type::PathBuf => format!("{r}.path()!!"), Type::Slice | Type::Vec | Type::Bytes if owned => format!("{r}.owned()!!"), Type::Slice | Type::Vec | Type::Bytes => format!("{r}.bytes()!!"), + Type::U128 => format!("{r}.u128()"), Type::Duration => format!("{r}.duration()"), Type::SocketAddr(_) => format!("{r}.socketAddr()"), Type::List(_, inner) => { @@ -645,7 +649,7 @@ impl<'a> Gen<'a> { "crate::{name} {{ vtable: &VTABLE_{name}, data: rt::global(env, {p}) }}" )); } - Ty::Duration | Ty::SocketAddr | Ty::Struct(_) | Ty::In(_) => { + Ty::U128 | Ty::Duration | Ty::SocketAddr | Ty::Struct(_) | Ty::In(_) => { let w = self.fresh("w"); // Taken by value, each object in a list moves out, as one passed alone does. if moved(abi, ty) { @@ -1962,7 +1966,7 @@ fn jni_type(c: &Ty, ty: &Type) -> (&'static str, &'static str) { Ty::Handle { .. } | Ty::Future(_) => ("jlong", "Long"), Ty::Buf => ("jbyteArray", "ByteArray?"), Ty::Struct(_) if matches!(ty.base(), Type::Callback(..)) => ("jobject", "Any"), - Ty::Duration | Ty::SocketAddr | Ty::Struct(_) | Ty::In(_) => ("jbyteArray", "ByteArray"), + Ty::U128 | Ty::Duration | Ty::SocketAddr | Ty::Struct(_) | Ty::In(_) => ("jbyteArray", "ByteArray"), Ty::Fn(..) => ("jobject", "Any"), Ty::Waker => ("jobject", "Waker"), Ty::Poll | Ty::Out(_) | Ty::Data => unreachable!("{c:?} never crosses JNI"), @@ -2031,6 +2035,7 @@ pub(crate) fn rust_c(abi: &Abi<'_>, c: &Ty) -> String { Ty::Char => "u32".to_string(), Ty::Enum(_) => "i32".to_string(), Ty::Buf => "crate::rt::Buf".to_string(), + Ty::U128 => "crate::rt::U128".to_string(), Ty::Duration => "crate::rt::Duration".to_string(), Ty::SocketAddr => "crate::rt::SocketAddr".to_string(), Ty::Struct(name) => format!("crate::{name}"), diff --git a/ffi/src/python.rs b/ffi/src/python.rs index fecc1a723..3f2a75b5c 100644 --- a/ffi/src/python.rs +++ b/ffi/src/python.rs @@ -398,9 +398,10 @@ impl<'a> Gen<'a> { fn py_ty(&self, ty: &Type) -> String { let abi = self.abi; match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => "None".to_string(), Type::Bool => "bool".to_string(), - Type::Int(_) => "int".to_string(), + Type::Int(_) | Type::U128 => "int".to_string(), Type::F32 | Type::F64 => "float".to_string(), Type::Char | Type::Str | Type::String | Type::Path | Type::PathBuf => "str".to_string(), Type::Slice | Type::Vec | Type::Bytes => "memoryview".to_string(), @@ -470,6 +471,7 @@ impl<'a> Gen<'a> { fn lift(&mut self, ty: &Type, o: &str, m: &str) -> String { let abi = self.abi; match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Bool => format!("rt::bool_in({o})"), Type::Int(int) => format!("rt::int_in::<{}>({o})", int.rust()), Type::F32 => format!("rt::float_in({o}).map(|f| f as f32)"), @@ -478,6 +480,7 @@ impl<'a> Gen<'a> { Type::Str | Type::String => format!("rt::str_in({o})"), Type::Slice | Type::Vec | Type::Bytes => format!("rt::bytes_in({o}, s)"), Type::Path | Type::PathBuf => format!("rt::path_in({o}, s)"), + Type::U128 => format!("rt::u128_in({o})"), Type::Duration => format!("rt::duration_in({o})"), Type::SocketAddr(family) => format!("rt::addr_in({o}, {})", family_of(*family)), Type::List(..) => format!("r_{}({o}, s, {m})", self.c_struct(ty)), @@ -533,6 +536,7 @@ impl<'a> Gen<'a> { fn lower(&mut self, ty: &Type, v: &str) -> String { let abi = self.abi; match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => "rt::none()".to_string(), Type::Bool => format!("rt::bool_out({v})"), Type::Int(int) if signed(*int) => format!("rt::int_out({v} as i64)"), @@ -542,6 +546,7 @@ impl<'a> Gen<'a> { Type::Str | Type::String => format!("rt::str_out({v})"), Type::Slice | Type::Vec | Type::Bytes => format!("rt::bytes_out({v})"), Type::Path | Type::PathBuf => format!("rt::path_out({v})"), + Type::U128 => format!("rt::u128_out({v})"), Type::Duration => format!("rt::duration_out({v})"), Type::SocketAddr(_) => format!("rt::addr_out({v})"), Type::List(..) => format!("w_{}({v})", self.c_struct(ty)), diff --git a/ffi/src/swift.rs b/ffi/src/swift.rs index 5f12c91b8..9201e4d35 100644 --- a/ffi/src/swift.rs +++ b/ffi/src/swift.rs @@ -68,6 +68,7 @@ pub fn generate(abi: &Abi<'_>, packages: &str, contract: u64) -> Result Swift.Bool {{\n\t\t{p}_contract() == 0x{contract:016x} && {upper}_CONTRACT == 0x{contract:016x}\n\t}}\n\n\ @@ -117,6 +118,7 @@ pub fn generate(abi: &Abi<'_>, packages: &str, contract: u64) -> Result Gen<'a> { fn sw(&self, ty: &Type) -> String { let abi = self.abi; match ty { + Type::Const(_) => unreachable!("a const argument is no value"), Type::Unit => "Void".to_string(), Type::Bool => "Swift.Bool".to_string(), Type::Int(int) => sw_int(*int).to_string(), @@ -236,6 +241,7 @@ impl<'a> Gen<'a> { Type::Char => "Swift.Unicode.Scalar".to_string(), Type::Str | Type::String | Type::Path | Type::PathBuf => "Swift.String".to_string(), Type::Slice | Type::Vec | Type::Bytes => "Data".to_string(), + Type::U128 => "U128".to_string(), Type::Duration => "Swift.Duration".to_string(), Type::SocketAddr(_) => "SocketAddr".to_string(), Type::List(_, inner) => format!("[{}]", self.sw(inner)), @@ -272,6 +278,7 @@ impl<'a> Gen<'a> { Ty::F64 => "Swift.Double".to_string(), Ty::Char => "Swift.UInt32".to_string(), Ty::Buf => abi.rt("buf"), + Ty::U128 => abi.rt("u128"), Ty::Duration => abi.rt("duration"), Ty::SocketAddr => abi.rt("socket_addr"), Ty::Struct(n) | Ty::Enum(n) => n.clone(), @@ -327,6 +334,7 @@ impl<'a> Gen<'a> { Type::Bool | Type::F32 | Type::F64 => e.to_string(), Type::Int(int) => lower_int(*int, e), Type::Char => format!("{e}.value"), + Type::U128 => format!("Native.u128({e})"), Type::Duration => format!("Native.duration({e})"), Type::SocketAddr(_) => format!("Native.socketAddr({e})"), Type::Range(_) | Type::Bound(_) | Type::Bounds(_) => format!("{e}._lower()"), @@ -384,6 +392,7 @@ impl<'a> Gen<'a> { Type::Bool | Type::F32 | Type::F64 => e.to_string(), Type::Int(int) => lift_int(*int, e), Type::Char => format!("Native.scalar({e})"), + Type::U128 => format!("Native.u128({e})"), Type::Duration => format!("Native.duration({e})"), Type::SocketAddr(_) => format!("Native.socketAddr({e})"), Type::Range(_) | Type::Bound(_) | Type::Bounds(_) => format!("{}._lift({e})", self.sw(ty)), @@ -1538,3 +1547,32 @@ fn indent(code: &str) -> String { } out } + +#[cfg(test)] +mod tests { + use super::{RUNTIME, TAKEN}; + + /// Each type the runtime declares at its top level, and each protocol one conforms to, + /// is a name no export may take, as either would clash with or shadow it. + #[test] + fn runtime_names_taken() { + for line in RUNTIME.lines() { + let Some(decl) = [ + "public struct ", + "public final class ", + "public enum ", + "public protocol ", + ] + .iter() + .find_map(|kind| line.strip_prefix(kind)) else { + continue; + }; + let decl = decl.trim_end_matches('{').trim(); + let (name, conforms) = decl.split_once(':').unwrap_or((decl, "")); + let names = std::iter::once(name).chain(conforms.split(',')); + for name in names.map(str::trim).filter(|n| !n.is_empty() && !n.starts_with('@')) { + assert!(TAKEN.contains(&name), "the runtime's `{name}` is not in TAKEN"); + } + } + } +} diff --git a/ffi/tests/api.c b/ffi/tests/api.c index 8bd088d53..e84bf17a0 100644 --- a/ffi/tests/api.c +++ b/ffi/tests/api.c @@ -441,6 +441,37 @@ static int script(void) { api_Name_free(other); printf("total %llu\n", (unsigned long long)api_total((api_range_u64){.start = 2, .end = 5})); + printf("reach %u\n", (unsigned)api_reach(2, 5)); + api_option_u128 scaled[] = { + api_scaled((api_u128){.lo = UINT64_MAX, .hi = UINT64_MAX}, 1), + api_scaled((api_u128){.lo = 1, .hi = 1}, 1ULL << 63), + api_scaled((api_u128){.hi = 1ULL << 63}, 2), + }; + printf("scaled"); + for (size_t i = 0; i < 3; i++) { + if (scaled[i].some) { + printf(" %016llx%016llx", (unsigned long long)scaled[i].value.hi, (unsigned long long)scaled[i].value.lo); + } else { + printf(" none"); + } + } + printf("\n"); + api_Batch *batch = api_Batch_new(); + unsigned accepted = 0; + for (uint32_t v = 1; v <= 5; v++) { + accepted += api_Batch_push(batch, v); + } + api_Batch_2 *batch2 = api_pair(); + unsigned paired = 0; + for (uint32_t v = 1; v <= 3; v++) { + paired += api_Batch_2_push(batch2, v); + } + unsigned long long emptied = api_drain_4(batch); + printf("batch %zu %u %llu %llu %zu %u %llu\n", api_Batch_capacity(batch), accepted, emptied, + (unsigned long long)api_drain_4(batch), api_Batch_2_capacity(batch2), paired, (unsigned long long)api_drain_2(batch2)); + printf("capacities %zu %zu\n", api_capacities_4_2(batch, batch2), api_capacities_2_2(batch2, batch2)); + api_Batch_2_free(batch2); + api_Batch_free(batch); api_range_u64 wide = api_widen((api_range_u64){.start = 2, .end = 5}, 1); printf("widen %llu %llu\n", (unsigned long long)wide.start, (unsigned long long)wide.end); api_bounds_u64 ranges[] = { @@ -571,6 +602,27 @@ static int script(void) { printf("nonempty pending\n"); } api_Channel_recv_nonempty_future_free(nonempty); + api_Batch *fill4 = api_Batch_new(); + api_Batch_2 *fill2 = api_pair(); + api_fill_4_future *filling4 = api_fill_4(fill4, 9); + api_fill_2_future *filling2 = api_fill_2(fill2, 1); + api_vec_u32 filled4, filled2; + if (api_fill_4_poll(filling4, waker, &filled4) == API_POLL_READY && api_fill_2_poll(filling2, waker, &filled2) == API_POLL_READY) { + printf("fill"); + for (size_t i = 0; i < filled4.len; i++) { + printf(" %u", (unsigned)filled4.ptr[i]); + } + for (size_t i = 0; i < filled2.len; i++) { + printf(" %u", (unsigned)filled2.ptr[i]); + } + printf("\n"); + api_vec_u32_free(filled4); + api_vec_u32_free(filled2); + } + api_fill_4_future_free(filling4); + api_fill_2_future_free(filling2); + api_Batch_2_free(fill2); + api_Batch_free(fill4); /* The send happens at the call: the future is dropped unpolled. */ api_Channel_send_later_future_free(api_Channel_send_later(channel, str("eager"))); @@ -1131,7 +1183,7 @@ static int script(void) { api_Token_free(twice); /* Each future dropped its waker once it finished, and Rust the one it was given. */ - return drops == 9 ? 0 : 1; + return drops == 11 ? 0 : 1; } /* Each breaks a contract, which must abort with a message naming the function. */ diff --git a/ffi/tests/api.cpp b/ffi/tests/api.cpp index c86258b10..3f45ac083 100644 --- a/ffi/tests/api.cpp +++ b/ffi/tests/api.cpp @@ -357,6 +357,32 @@ int script() { } std::printf("total %llu\n", ull(api::total({2, 5}))); + std::printf("reach %u\n", unsigned(api::reach(2, 5))); + std::printf("scaled"); + for (auto v : {api::scaled({UINT64_MAX, UINT64_MAX}, 1), api::scaled({1, 1}, 1ULL << 63), api::scaled({0, 1ULL << 63}, 2)}) { + if (v) { + std::printf(" %016llx%016llx", ull(v->hi), ull(v->lo)); + } else { + std::printf(" none"); + } + } + std::printf("\n"); + { + auto batch = api::Batch::new_(); + unsigned accepted = 0; + for (std::uint32_t v = 1; v <= 5; v++) { + accepted += batch.push(v); + } + auto batch2 = api::pair(); + unsigned paired = 0; + for (std::uint32_t v = 1; v <= 3; v++) { + paired += batch2.push(v); + } + auto emptied = api::drain_4(batch); + std::printf("batch %zu %u %llu %llu %zu %u %llu\n", batch.capacity(), accepted, ull(emptied), ull(api::drain_4(batch)), + batch2.capacity(), paired, ull(api::drain_2(batch2))); + std::printf("capacities %zu %zu\n", api::capacities_4_2(batch, batch2), api::capacities_2_2(batch2, batch2)); + } api::range_u64 wide = api::widen({2, 5}, 1); std::printf("widen %llu %llu\n", ull(wide.start), ull(wide.end)); api::bounds_u64 ranges[] = { @@ -471,6 +497,22 @@ int script() { std::printf("nonempty pending\n"); } } + { + auto fill4 = api::Batch::new_(); + auto fill2 = api::pair(); + auto filled4 = api::fill_4(fill4, 9).poll(waker); + auto filled2 = api::fill_2(fill2, 1).poll(waker); + if (filled4 && filled2) { + std::printf("fill"); + for (auto v : *filled4) { + std::printf(" %u", unsigned(v)); + } + for (auto v : *filled2) { + std::printf(" %u", unsigned(v)); + } + std::printf("\n"); + } + } // The send happens at the call: the future is dropped unpolled. channel.send_later("eager"); diff --git a/ffi/tests/api.dart b/ffi/tests/api.dart index c361bf511..7d724153b 100644 --- a/ffi/tests/api.dart +++ b/ffi/tests/api.dart @@ -240,6 +240,21 @@ Future script() async { print('borrowed ${lit.asStr()} ${lit.shares(api.Name.borrowed('lit'))} ${lit.shares(api.Name.borrowed('other'))}'); print('total ${api.total(const api.RangeU64(2, 5))}'); + print('reach ${api.reach(2, 5)}'); + final scaled = [ + api.scaled((BigInt.one << 128) - BigInt.one, 1), + api.scaled((BigInt.one << 64) + BigInt.one, 1 << 63), + api.scaled(BigInt.one << 127, 2), + ]; + print('scaled ${scaled.map((v) => v?.toRadixString(16).padLeft(32, '0') ?? 'none').join(' ')}'); + final batch = api.Batch(), batch2 = api.pair(); + final accepted = [for (var v = 1; v <= 5; v++) batch.push(v)].where((p) => p).length; + final paired = [for (var v = 1; v <= 3; v++) batch2.push(v)].where((p) => p).length; + final emptied = api.drain4(batch); + print('batch ${batch.capacity()} $accepted $emptied ${api.drain4(batch)} ${batch2.capacity()} $paired ${api.drain2(batch2)}'); + print('capacities ${api.capacities42(batch, batch2)} ${api.capacities22(batch2, batch2)}'); + batch.close(); + batch2.close(); final wide = api.widen(const api.RangeU64(2, 5), 1); print('widen ${wide.start} ${wide.end}'); final counts = [ @@ -329,6 +344,10 @@ Future script() async { } on api.Error catch (e) { print('nonempty err $e'); } + final fill4 = api.Batch(), fill2 = api.pair(); + print('fill ${[...await api.fill4(fill4, 9), ...await api.fill2(fill2, 1)].join(' ')}'); + fill4.close(); + fill2.close(); // The send happens at the call: the future is dropped unawaited. channel.sendLater(utf8.encode('eager')); print('recv ${text(await channel.recv())}'); @@ -922,6 +941,8 @@ void bounds() { throws(() => api.nextChar(String.fromCharCode(0xd800)), 'a low surrogate'); throws(() => api.nextChar(String.fromCharCode(0xdfff)), 'a high surrogate'); check(api.nextChar('\u{d7ff}') == null, 'the scalar before the surrogates'); + throws(() => api.scaled(BigInt.one << 128, 1), 'an int past u128'); + throws(() => api.scaled(-BigInt.one, 1), 'a negative int for a u128'); // Dart's longest Duration is 9223372036854 seconds and 775807 microseconds. for (final us in [9223372036854000000, 9223372036854775806]) { diff --git a/ffi/tests/api.go b/ffi/tests/api.go index 620a5b1a2..fcd596a4f 100644 --- a/ffi/tests/api.go +++ b/ffi/tests/api.go @@ -13,6 +13,7 @@ import ( "fmt" "io" "math" + "math/big" "net/netip" "os" "runtime" @@ -302,6 +303,38 @@ func script() { fmt.Printf("borrowed %s %t %t\n", must(lit.AsStr()), must(lit.Shares(api.NameBorrowed("lit"))), must(lit.Shares(api.NameBorrowed("other")))) fmt.Printf("total %d\n", api.Total(rt.RangeU64{Start: 2, End: 5})) + fmt.Printf("reach %d\n", api.Reach(2, 5)) + bigOne := big.NewInt(1) + pow := func(n uint) *big.Int { return new(big.Int).Lsh(bigOne, n) } + fmt.Print("scaled") + for _, v := range []*big.Int{ + api.Scaled(new(big.Int).Sub(pow(128), bigOne), 1), + api.Scaled(new(big.Int).Add(pow(64), bigOne), 1<<63), + api.Scaled(pow(127), 2), + } { + if v == nil { + fmt.Print(" none") + } else { + fmt.Printf(" %032x", v) + } + } + fmt.Println() + batch, batch2 := api.NewBatch(), api.Pair() + accepted, paired := 0, 0 + for v := uint32(1); v <= 5; v++ { + if must(batch.Push(v)) { + accepted++ + } + } + for v := uint32(1); v <= 3; v++ { + if must(batch2.Push(v)) { + paired++ + } + } + emptied := must(api.Drain4(batch)) + fmt.Printf("batch %d %d %d %d %d %d %d\n", must(batch.Capacity()), accepted, emptied, must(api.Drain4(batch)), + must(batch2.Capacity()), paired, must(api.Drain2(batch2))) + fmt.Printf("capacities %d %d\n", must(api.Capacities42(batch, batch2)), must(api.Capacities22(batch2, batch2))) wide := api.Widen(rt.RangeU64{Start: 2, End: 5}, 1) fmt.Printf("widen %d %d\n", wide.Start, wide.End) included := func(n uint64) rt.BoundU64 { return rt.BoundU64{Kind: rt.BoundIncluded, Value: n} } @@ -397,6 +430,11 @@ func script() { if _, err := channel.RecvNonempty(ctx); err != nil { fmt.Printf("nonempty err %s\n", err) } + fmt.Print("fill") + for _, v := range append(must(api.Fill4(ctx, api.NewBatch(), 9)), must(api.Fill2(ctx, api.Pair(), 1))...) { + fmt.Printf(" %d", v) + } + fmt.Println() // The send happens at the call: the future is cancelled before its first poll. cancelled, cancel := context.WithCancel(ctx) cancel() diff --git a/ffi/tests/api.kt b/ffi/tests/api.kt index 837f8067c..3f8f71d59 100644 --- a/ffi/tests/api.kt +++ b/ffi/tests/api.kt @@ -168,6 +168,22 @@ fun script() { println("borrowed ${lit.asStr()} ${lit.shares(Name.borrowed("lit"))} ${lit.shares(Name.borrowed("other"))}") println("total ${total(RangeU64(2u, 5u))}") + println("reach ${reach(2u, 5u)}") + val two = java.math.BigInteger.TWO + val scaled = listOf(scaled(two.pow(128) - java.math.BigInteger.ONE, 1u), scaled(two.pow(64) + java.math.BigInteger.ONE, 1uL shl 63), scaled(two.pow(127), 2u)) + println("scaled " + scaled.joinToString(" ") { it?.toString(16)?.padStart(32, '0') ?: "none" }) + for (v in listOf(two.pow(128), java.math.BigInteger.ONE.negate())) { + check(runCatching { scaled(v, 1u) }.exceptionOrNull() is IllegalArgumentException, "$v is no u128") + } + Batch.new().use { batch -> + pair().use { batch2 -> + val accepted = (1u..5u).count { batch.push(it) } + val paired = (1u..3u).count { batch2.push(it) } + val emptied = drain4(batch) + println("batch ${batch.capacity()} $accepted $emptied ${drain4(batch)} ${batch2.capacity()} $paired ${drain2(batch2)}") + println("capacities ${capacities42(batch, batch2)} ${capacities22(batch2, batch2)}") + } + } val wide = widen(RangeU64(2u, 5u), 1u) println("widen ${wide.start} ${wide.end}") val counts = listOf( @@ -258,6 +274,9 @@ fun script() { } catch (e: ErrorException) { println("nonempty err ${e.message}") } + Batch.new().use { b4 -> + pair().use { b2 -> println("fill " + (fill4(b4, 9u) + fill2(b2, 1u)).joinToString(" ")) } + } // The send happens at the call: the future is dropped unawaited. channel.sendLater("eager".encodeToByteArray()) println("recv ${channel.recv().text()}") diff --git a/ffi/tests/api.py b/ffi/tests/api.py index 984db7782..2382f2a9a 100644 --- a/ffi/tests/api.py +++ b/ffi/tests/api.py @@ -234,6 +234,15 @@ def edit(counter): print(f"borrowed {lit.as_str()} {' '.join(shares)}") print(f"total {api.total(range(2, 5))}") + print(f"reach {api.reach(2, 5)}") + scaled = [api.scaled(2**128 - 1, 1), api.scaled(2**64 + 1, 2**63), api.scaled(2**127, 2)] + print("scaled " + " ".join("none" if v is None else f"{v:032x}" for v in scaled)) + batch, batch2 = api.Batch.new(), api.pair() + accepted = sum(batch.push(v) for v in range(1, 6)) + paired = sum(batch2.push(v) for v in range(1, 4)) + emptied = api.drain_4(batch) + print(f"batch {batch.capacity()} {accepted} {emptied} {api.drain_4(batch)} {batch2.capacity()} {paired} {api.drain_2(batch2)}") + print(f"capacities {api.capacities_4_2(batch, batch2)} {api.capacities_2_2(batch2, batch2)}") wide = api.widen(range(2, 5), 1) print(f"widen {wide.start} {wide.stop}") inc, exc, unb = api.Bound.INCLUDED, api.Bound.EXCLUDED, api.Bound.UNBOUNDED @@ -339,6 +348,8 @@ async def block(): print(f"nonempty {payload.nbytes}") except api.ErrorException as e: print(f"nonempty err {e}") + filled = await api.fill_4(api.Batch.new(), 9) + await api.fill_2(api.pair(), 1) + print("fill " + " ".join(map(str, filled))) # The send happens at the call: the future is dropped unawaited. channel.send_later(b"eager") print(f"recv {text(await channel.recv())}") @@ -817,6 +828,8 @@ def raises(error, f, what): raises(OverflowError, lambda: api.widths(2**64), "an int past u64 raises") raises(OverflowError, lambda: api.widths(-1), "a negative int for a u64 raises") raises(TypeError, lambda: api.title(1), "an int is no bool") + raises(OverflowError, lambda: api.scaled(2**128, 1), "an int past u128 raises") + raises(OverflowError, lambda: api.scaled(-1, 1), "a negative int for a u128 raises") # A borrowed tuple is one C value, not one out per element. check(api.corner(True) == (1, 1), "a borrowed tuple returns whole") diff --git a/ffi/tests/api.swift b/ffi/tests/api.swift index afe3cd69e..706e0f6d1 100644 --- a/ffi/tests/api.swift +++ b/ffi/tests/api.swift @@ -281,6 +281,20 @@ func script() { print("borrowed \(lit.asStr()) \(lit.shares(Name.borrowed("lit"))) \(lit.shares(Name.borrowed("other")))") print("total \(total(RangeU64(start: 2, end: 5)))") + print("reach \(reach(2, 5))") + let scaledHex = [ + scaled(U128(high: .max, low: .max), 1), scaled(U128(high: 1, low: 1), 1 << 63), scaled(U128(high: 1 << 63, low: 0), 2), + ].map { v in + v.map { v in [v.high, v.low].map { h in Swift.String(repeating: "0", count: 16 - Swift.String(h, radix: 16).count) + Swift.String(h, radix: 16) }.joined() } ?? "none" + } + print("scaled \(scaledHex.joined(separator: " "))") + check(U128(high: .max, low: .max).description == "340282366920938463463374607431768211455", "a u128's decimal digits") + let batch = Batch.new(), batch2 = pair() + let accepted = (1...5).filter { batch.push($0) }.count + let paired = (1...3).filter { batch2.push($0) }.count + let emptied = drain4(batch) + print("batch \(batch.capacity()) \(accepted) \(emptied) \(drain4(batch)) \(batch2.capacity()) \(paired) \(drain2(batch2))") + print("capacities \(capacities42(batch, batch2)) \(capacities22(batch2, batch2))") let wide = widen(RangeU64(start: 2, end: 5), 1) print("widen \(wide.start) \(wide.end)") let counts = [ @@ -384,6 +398,8 @@ func script() { } catch { print("nonempty err \(error)") } + let filled = try await fill4(Batch.new(), 9) + fill2(Api.pair(), 1) + print("fill \(filled.map { Swift.String($0) }.joined(separator: " "))") // The send happens at the call, before the future is first polled. try await channel.sendLater(Data("eager".utf8)) print("recv \(text(try await channel.recv()))") diff --git a/ffi/tests/fixtures.rs b/ffi/tests/fixtures.rs index 6636bff03..8b7393ded 100644 --- a/ffi/tests/fixtures.rs +++ b/ffi/tests/fixtures.rs @@ -1204,6 +1204,7 @@ fn relative_target() { let text = std::fs::read_to_string(census.join("api-fixture.md")).unwrap(); for line in [ "- `Lease`: object no export returns", + "- `Void`: object no export returns", "- `release`: type C never holds `Lease`", "- `Unnamed`: generic type no export instantiates", ] { diff --git a/quest/m1/ffi-export/refusals/small-shapes.md b/quest/m1/ffi-export/refusals/small-shapes.md index 13ee06737..9394be8ae 100644 --- a/quest/m1/ffi-export/refusals/small-shapes.md +++ b/quest/m1/ffi-export/refusals/small-shapes.md @@ -1,14 +1,27 @@ -# [S] Small shapes export +# [XS] Arrays export ## Goal -The census at the moq pin refuses no item of moq's own for a small shape: 26 items at moq -`ffcb7cd9` use `u128` (`Timestamp::as_nanos`), an array, a const generic, unit outside a -return, `Result` outside a return, `!`, or an enum without variants. The count includes -the objects and futures that are not `Send`, which -[not-send](/quest/m1/ffi-export/not-send.md) owns since 2026-11-09. +The census at the moq pin refuses no item of moq's own for an array. Two are left: +`tokio::tls::parse_fingerprint` returns `Result<[u8; 32]>`, a moq-tokio item the narrowed +stand-in still exports, and `mux::codec::flac::Config` has a `pub md5: [u8; 16]` field, +which its getter returns and its setter takes. ## Plan -Each is its own small mapping; what has no C shape keeps its refusal and goes to the -maintainer as moq's signature to change. +Changing moq is a non-goal: the array gets a C shape here, never a request for moq to +change its signature. + +`u128`, const generics, a `()` parameter, and an enum without variants export since +[moq-dev/rust2#675](https://github.com/moq-dev/rust2/pull/675); `Result` and `!` off a +return left the census with the narrowed stand-in. + +## Waiting + +- Maintainer: how `[T; N]` maps. Options: (a) a returned `[u8; N]` is bytes, as `Vec` + is, and a parameter `[u8; N]` is bytes whose length the glue checks, raising the + language's error on a mismatch; (b) a C struct `_array__` holding the + array, with each language's fixed-size form above it; (c) leave arrays refused and + keep this quest open until a consumer needs one. Recommended: (a), which reuses the + bytes every binding already has and clears both with no new type; any other element + type stays refused until one is needed.