mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-11-30 12:33:54 +03:00
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:
parent
0c09e154cf
commit
c80bf9a323
@ -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`.
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
12
examples/guide-supported-types-examples/non_null.js
Normal file
12
examples/guide-supported-types-examples/non_null.js
Normal 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);
|
13
examples/guide-supported-types-examples/src/non_null.rs
Normal file
13
examples/guide-supported-types-examples/src/non_null.rs
Normal 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))))
|
||||
}
|
@ -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)
|
||||
|
17
guide/src/reference/types/non-null.md
Normal file
17
guide/src/reference/types/non-null.md
Normal 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}}
|
||||
```
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user