Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
3 changes: 3 additions & 0 deletions crates/cli-support/src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ tys! {
RESULT
UNIT
CLAMPED
NONNULL
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -72,6 +73,7 @@ pub enum Descriptor {
Option(Box<Descriptor>),
Result(Box<Descriptor>),
Unit,
NonNull,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -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),
}
}
Expand Down
17 changes: 15 additions & 2 deletions crates/cli-support/src/js/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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"),
Expand Down
8 changes: 8 additions & 0 deletions crates/cli-support/src/wit/incoming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion crates/cli-support/src/wit/outgoing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ impl InstructionBuilder<'_, '_> {

// Largely synthetic and can't show up
Descriptor::ClampedU8 => unreachable!(),

Descriptor::NonNull => self.outgoing_i32(AdapterType::NonNull),
daxpedda marked this conversation as resolved.
Show resolved Hide resolved
}
Ok(())
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions crates/cli-support/src/wit/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub enum AdapterType {
Enum(String),
NamedExternref(String),
Function,
NonNull,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -308,6 +309,8 @@ pub enum Instruction {
OptionEnumFromI32 {
hole: u32,
},
I32FromOptionNonNull,
OptionNonNullFromI32,
}

impl AdapterType {
Expand Down
2 changes: 1 addition & 1 deletion crates/shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/shared/src/schema_hash_approval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
12 changes: 12 additions & 0 deletions examples/guide-supported-types-examples/non_null.js
Original file line number Diff line number Diff line change
@@ -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 changes: 13 additions & 0 deletions examples/guide-supported-types-examples/src/non_null.rs
Original file line number Diff line number Diff line change
@@ -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))))
}
1 change: 1 addition & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions guide/src/reference/types/non-null.md
Original file line number Diff line number Diff line change
@@ -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 changes: 1 addition & 1 deletion guide/src/reference/types/pointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
62 changes: 62 additions & 0 deletions src/convert/impls.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand Down
9 changes: 9 additions & 0 deletions src/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#![doc(hidden)]

use core::ptr::NonNull;

use crate::{Clamped, JsError, JsObject, JsValue};
use cfg_if::cfg_if;

Expand Down Expand Up @@ -46,6 +48,7 @@ tys! {
RESULT
UNIT
CLAMPED
NONNULL
}

#[inline(always)] // see the wasm-interpreter crate
Expand Down Expand Up @@ -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);
Expand Down
36 changes: 36 additions & 0 deletions tests/wasm/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Loading