Skip to content

Commit

Permalink
Add support for Option<*const T>, Option<*mut T> and NonNull<T> (
Browse files Browse the repository at this point in the history
…#3852)

Co-authored-by: Liam Murphy <43807659+Liamolucko@users.noreply.github.com>
  • Loading branch information
daxpedda and Liamolucko committed Feb 26, 2024
1 parent 0c09e15 commit c80bf9a
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 6 deletions.
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),
}
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

0 comments on commit c80bf9a

Please sign in to comment.