Add support for Option<*const T>, Option<*mut T> and NonNull<T> (#3852)

Co-authored-by: Liam Murphy <43807659+Liamolucko@users.noreply.github.com>
This commit is contained in:
daxpedda 2024-02-26 11:49:55 +01:00 committed by GitHub
parent 0c09e154cf
commit c80bf9a323
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 256 additions and 6 deletions

View File

@ -14,6 +14,9 @@
* Add `TryFrom` implementations for `Number`, that allow losslessly converting from 64- and 128-bits numbers.
[#3847](https://github.com/rustwasm/wasm-bindgen/pull/3847)
* Add support for `Option<*const T>`, `Option<*mut T>` and `NonNull<T>`.
[#3852](https://github.com/rustwasm/wasm-bindgen/pull/3852)
### Fixed
* Make .wasm output deterministic when using `--reference-types`.

View File

@ -40,6 +40,7 @@ tys! {
RESULT
UNIT
CLAMPED
NONNULL
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -72,6 +73,7 @@ pub enum Descriptor {
Option(Box<Descriptor>),
Result(Box<Descriptor>),
Unit,
NonNull,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -165,6 +167,7 @@ impl Descriptor {
CHAR => Descriptor::Char,
UNIT => Descriptor::Unit,
CLAMPED => Descriptor::_decode(data, true),
NONNULL => Descriptor::NonNull,
other => panic!("unknown descriptor: {}", other),
}
}

View File

@ -652,7 +652,7 @@ fn instruction(
Instruction::WasmToInt { output, .. } => {
let val = js.pop();
match output {
AdapterType::U32 => js.push(format!("{} >>> 0", val)),
AdapterType::U32 | AdapterType::NonNull => js.push(format!("{} >>> 0", val)),
AdapterType::U64 => js.push(format!("BigInt.asUintN(64, {val})")),
_ => js.push(val),
}
@ -1217,6 +1217,18 @@ fn instruction(
let val = js.pop();
js.push(format!("{0} === {1} ? undefined : {0}", val, hole));
}
Instruction::I32FromOptionNonNull => {
let val = js.pop();
js.cx.expose_is_like_none();
js.assert_optional_number(&val);
js.push(format!("isLikeNone({0}) ? 0 : {0}", val));
}
Instruction::OptionNonNullFromI32 => {
let val = js.pop();
js.push(format!("{0} === 0 ? undefined : {0} >>> 0", val));
}
}
Ok(())
}
@ -1324,7 +1336,8 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String) {
| AdapterType::U16
| AdapterType::U32
| AdapterType::F32
| AdapterType::F64 => dst.push_str("number"),
| AdapterType::F64
| AdapterType::NonNull => dst.push_str("number"),
AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("bigint"),
AdapterType::String => dst.push_str("string"),
AdapterType::Externref => dst.push_str("any"),

View File

@ -155,6 +155,8 @@ impl InstructionBuilder<'_, '_> {
// Largely synthetic and can't show up
Descriptor::ClampedU8 => unreachable!(),
Descriptor::NonNull => unimplemented!("converting `NonNull<T>` from Wasm to Rust is not implemented"),
}
Ok(())
}
@ -331,6 +333,12 @@ impl InstructionBuilder<'_, '_> {
);
}
Descriptor::NonNull => self.instruction(
&[AdapterType::NonNull.option()],
Instruction::I32FromOptionNonNull,
&[AdapterType::I32],
),
_ => bail!(
"unsupported optional argument type for calling Rust function from JS: {:?}",
arg

View File

@ -156,6 +156,8 @@ impl InstructionBuilder<'_, '_> {
// Largely synthetic and can't show up
Descriptor::ClampedU8 => unreachable!(),
Descriptor::NonNull => self.outgoing_i32(AdapterType::NonNull),
}
Ok(())
}
@ -319,6 +321,12 @@ impl InstructionBuilder<'_, '_> {
);
}
Descriptor::NonNull => self.instruction(
&[AdapterType::I32],
Instruction::OptionNonNullFromI32,
&[AdapterType::NonNull.option()],
),
_ => bail!(
"unsupported optional argument type for calling JS function from Rust: {:?}",
arg
@ -350,7 +358,8 @@ impl InstructionBuilder<'_, '_> {
| Descriptor::CachedString
| Descriptor::Option(_)
| Descriptor::Vector(_)
| Descriptor::Unit => {
| Descriptor::Unit
| Descriptor::NonNull => {
// We must throw before reading the Ok type, if there is an error. However, the
// structure of ResultAbi is that the Err value + discriminant come last (for
// alignment reasons). So the UnwrapResult instruction must come first, but the

View File

@ -88,6 +88,7 @@ pub enum AdapterType {
Enum(String),
NamedExternref(String),
Function,
NonNull,
}
#[derive(Debug, Clone)]
@ -308,6 +309,8 @@ pub enum Instruction {
OptionEnumFromI32 {
hole: u32,
},
I32FromOptionNonNull,
OptionNonNullFromI32,
}
impl AdapterType {

View File

@ -6,7 +6,7 @@ mod schema_hash_approval;
// This gets changed whenever our schema changes.
// At this time versions of wasm-bindgen and wasm-bindgen-cli are required to have the exact same
// SCHEMA_VERSION in order to work together.
pub const SCHEMA_VERSION: &str = "0.2.88";
pub const SCHEMA_VERSION: &str = "0.2.92";
#[macro_export]
macro_rules! shared_api {

View File

@ -8,7 +8,7 @@
// If the schema in this library has changed then:
// 1. Bump the version in `crates/shared/Cargo.toml`
// 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version
const APPROVED_SCHEMA_FILE_HASH: &str = "2548486983363536439";
const APPROVED_SCHEMA_FILE_HASH: &str = "11955579329744078753";
#[test]
fn schema_version() {

View File

@ -0,0 +1,12 @@
import {
take_pointer_by_value,
return_pointer,
} from './guide_supported_types_examples';
import { memory } from './guide_supported_types_examples_bg';
let ptr = return_pointer();
let buf = new Uint8Array(memory.buffer);
let value = buf[ptr];
console.log(`The byte at the ${ptr} address is ${value}`);
take_pointer_by_value(ptr);

View File

@ -0,0 +1,13 @@
use std::ptr;
use std::ptr::NonNull;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub unsafe fn take_pointer_by_value(x: Option<NonNull<u8>>) {
Box::from_raw(x.unwrap().as_ptr());
}
#[wasm_bindgen]
pub fn return_pointer() -> Option<NonNull<u8>> {
Some(NonNull::from(Box::leak(Box::new(42))))
}

View File

@ -54,6 +54,7 @@
- [`JsValue`](./reference/types/jsvalue.md)
- [`Box<[T]>` and `Vec<T>`](./reference/types/boxed-slices.md)
- [`*const T` and `*mut T`](./reference/types/pointers.md)
- [`NonNull<T>`](./reference/types/non-null.md)
- [Numbers](./reference/types/numbers.md)
- [`bool`](./reference/types/bool.md)
- [`char`](./reference/types/char.md)

View File

@ -0,0 +1,17 @@
# `NonNull<T>`
| `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | `Option<T>` parameter | `Option<T>` return value | JavaScript representation |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| No | No | No | Yes | Yes | Yes | A JavaScript number value |
## Example Rust Usage
```rust
{{#include ../../../../examples/guide-supported-types-examples/src/non_null.rs}}
```
## Example JavaScript Usage
```js
{{#include ../../../../examples/guide-supported-types-examples/non_null.js}}
```

View File

@ -2,7 +2,7 @@
| `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | `Option<T>` parameter | `Option<T>` return value | JavaScript representation |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| Yes | No | No | Yes | No | No | A JavaScript number value |
| Yes | No | No | Yes | Yes | Yes | A JavaScript number value |
## Example Rust Usage

View File

@ -1,5 +1,6 @@
use core::char;
use core::mem::{self, ManuallyDrop};
use core::ptr::NonNull;
use crate::convert::traits::{WasmAbi, WasmPrimitive};
use crate::convert::TryFromJsValue;
@ -223,6 +224,24 @@ impl<T> FromWasmAbi for *const T {
}
}
impl<T> IntoWasmAbi for Option<*const T> {
type Abi = Option<u32>;
#[inline]
fn into_abi(self) -> Option<u32> {
self.map(|ptr| ptr as u32)
}
}
impl<T> FromWasmAbi for Option<*const T> {
type Abi = Option<u32>;
#[inline]
unsafe fn from_abi(js: Option<u32>) -> Option<*const T> {
js.map(|ptr| ptr as *const T)
}
}
impl<T> IntoWasmAbi for *mut T {
type Abi = u32;
@ -241,6 +260,49 @@ impl<T> FromWasmAbi for *mut T {
}
}
impl<T> IntoWasmAbi for Option<*mut T> {
type Abi = Option<u32>;
#[inline]
fn into_abi(self) -> Option<u32> {
self.map(|ptr| ptr as u32)
}
}
impl<T> FromWasmAbi for Option<*mut T> {
type Abi = Option<u32>;
#[inline]
unsafe fn from_abi(js: Option<u32>) -> Option<*mut T> {
js.map(|ptr| ptr as *mut T)
}
}
impl<T> IntoWasmAbi for NonNull<T> {
type Abi = u32;
#[inline]
fn into_abi(self) -> u32 {
self.as_ptr() as u32
}
}
impl<T> OptionIntoWasmAbi for NonNull<T> {
#[inline]
fn none() -> u32 {
0
}
}
impl<T> FromWasmAbi for Option<NonNull<T>> {
type Abi = u32;
#[inline]
unsafe fn from_abi(js: Self::Abi) -> Self {
NonNull::new(js as *mut T)
}
}
impl IntoWasmAbi for JsValue {
type Abi = u32;

View File

@ -3,6 +3,8 @@
#![doc(hidden)]
use core::ptr::NonNull;
use crate::{Clamped, JsError, JsObject, JsValue};
use cfg_if::cfg_if;
@ -46,6 +48,7 @@ tys! {
RESULT
UNIT
CLAMPED
NONNULL
}
#[inline(always)] // see the wasm-interpreter crate
@ -114,6 +117,12 @@ impl<T> WasmDescribe for *mut T {
}
}
impl<T> WasmDescribe for NonNull<T> {
fn describe() {
inform(NONNULL)
}
}
impl<T: WasmDescribe> WasmDescribe for [T] {
fn describe() {
inform(SLICE);

View File

@ -110,3 +110,39 @@ exports.test_string_roundtrip = () => {
test('a longer string');
test('a longer 💖 string');
};
exports.test_raw_pointers = function() {
const memory32 = new Uint32Array(wasm.__wasm.memory.buffer);
const memory8 = new Uint8Array(wasm.__wasm.memory.buffer);
const ptr1 = wasm.simple_return_raw_pointer_u32(4294967295);
assert.strictEqual(memory32[ptr1 / 4], 4294967295);
const ptr2 = wasm.simple_return_raw_pointer_u8(42);
assert.strictEqual(memory8[ptr2], 42);
wasm.simple_raw_pointers_work(ptr1, ptr2);
assert.strictEqual(memory32[ptr1 / 4], 42);
const ptr3 = wasm.simple_return_raw_pointer_u32(4294967295);
wasm.simple_option_raw_pointers_work(ptr3, ptr2);
assert.strictEqual(memory32[ptr3 / 4], 42);
assert.strictEqual(wasm.simple_option_raw_pointers_work(0, ptr2), undefined);
assert.strictEqual(wasm.simple_option_raw_pointers_work(null, ptr2), undefined);
assert.strictEqual(wasm.simple_option_raw_pointers_work(undefined, ptr2), undefined);
assert.strictEqual(wasm.simple_option_raw_pointers_work(ptr1, 0), undefined);
assert.strictEqual(wasm.simple_option_raw_pointers_work(ptr1, null), undefined);
assert.strictEqual(wasm.simple_option_raw_pointers_work(ptr1, undefined), undefined);
assert.strictEqual(wasm.simple_return_option_null_pointer(), 0)
};
exports.test_non_null = function() {
assert.strictEqual(wasm.simple_option_nonnull_work(0), undefined);
assert.strictEqual(wasm.simple_option_nonnull_work(null), undefined);
assert.strictEqual(wasm.simple_option_nonnull_work(undefined), undefined);
assert.strictEqual(wasm.simple_option_nonnull_work(wasm.simple_return_non_null()), 42);
assert.strictEqual(wasm.simple_option_nonnull_work(wasm.simple_return_option_non_null(43)), 43);
};

View File

@ -1,3 +1,5 @@
use std::ptr::{self, NonNull};
use wasm_bindgen::prelude::*;
use wasm_bindgen::{intern, unintern};
use wasm_bindgen_test::*;
@ -29,6 +31,9 @@ extern "C" {
fn new_renamed() -> Renamed;
fn test_string_roundtrip();
fn test_raw_pointers();
fn test_non_null();
}
#[wasm_bindgen_test]
@ -56,12 +61,68 @@ pub fn simple_return_and_take_bool(a: bool, b: bool) -> bool {
a && b
}
#[wasm_bindgen]
pub fn simple_return_raw_pointer_u32(value: u32) -> *mut u32 {
Box::into_raw(Box::new(value))
}
#[wasm_bindgen]
pub fn simple_return_raw_pointer_u8(value: u8) -> *const u8 {
Box::into_raw(Box::new(value))
}
#[wasm_bindgen]
pub unsafe fn simple_raw_pointers_work(a: *mut u32, b: *const u8) -> *const u32 {
(*a) = (*b) as u32;
a
}
#[wasm_bindgen]
pub fn simple_return_option_null_pointer() -> Option<*const u32> {
Some(ptr::null())
}
#[wasm_bindgen]
pub unsafe fn simple_option_raw_pointers_work(
a: Option<*mut u32>,
b: Option<*const u8>,
) -> Option<*const u32> {
let a = a.and_then(|ptr| ptr.as_mut());
let b = b.and_then(|ptr| ptr.as_ref());
if let (Some(a), Some(b)) = (a, b) {
*a = *b as u32;
Some(a)
} else {
None
}
}
#[wasm_bindgen_test]
fn raw_pointers() {
test_raw_pointers();
}
#[wasm_bindgen]
pub fn simple_return_non_null() -> NonNull<u32> {
NonNull::from(Box::leak(Box::new(42)))
}
#[wasm_bindgen]
pub fn simple_return_option_non_null(value: u32) -> Option<NonNull<u32>> {
Some(NonNull::from(Box::leak(Box::new(value))))
}
#[wasm_bindgen]
pub unsafe fn simple_option_nonnull_work(a: Option<NonNull<u32>>) -> Option<u32> {
a.map(|ptr| *Box::from_raw(ptr.as_ptr()))
}
#[wasm_bindgen_test]
fn non_null() {
test_non_null();
}
#[wasm_bindgen_test]
fn string_arguments() {
test_string_arguments();