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

test+doc(c-api) Fix vecs that must be boxed vecs + don't transmute uninitialized boxed vecs #1949

Merged
merged 18 commits into from
Dec 18, 2020

Conversation

Hywan
Copy link
Contributor

@Hywan Hywan commented Dec 17, 2020

Description

This PR improve test coverage of the C API.

It's a work in progress. I've paused this work because we need to focus on something else, but one of the two test cases that have been added reveals an important bug.

The wasm_frame_vec_t, wasm_functype_vec_t, wasm_globaltype_vec_t, wasm_memorytype_vec_t and wasm_tabletype_vec_t are incorrectly defined. Indeed, in wasm.h they are all defined with WASM_DECLARE_TYPE (except for wasm_frame_vec_t, but it's exactly the same), which declares the vectors as WAM_DECLARE_VEC(name, *). This * is important. It means that we must not declare thoses types with our own wasm_declare_vec! macro, but rather with wasm_declare_boxed_vec!.

Fixing this bug impact many places in the code. I didn't have time for the moment, but it's probably not a big deal! This PR fixes those bugs with e10ad51, 75ceb0d, a3c7a2d, a0c34fe. Bonus for wasm_frame_t that was also incorrectly implemented, the fix lands in 4abf6f8 and is possible thanks to 43026e6, with a side effect of simplifying the code in 940dea7.

While continuing to improve the test coverage, another bug has been found. In case of a boxed vector, wasm_$name_vec_delete was crashing if the given vector was uninitialized. Basically the following code was crashing:

wasm_exporttype_vec_t vector;
wasm_exporttype_vec_new_uninitialized(&vector, 3);
wasm_exporttype_vec_delete(&vector);

This is now fixed in 8aa0822, and tested in 96169de.

The PR adds 34 test cases, and has found+fixes 6 bugs.

Review

  • Add a short description of the the change to the CHANGELOG.md file

@Hywan Hywan added bug Something isn't working 📦 lib-c-api About wasmer-c-api 🧪 tests I love tests 📚 documentation Do you like to read? labels Dec 17, 2020
@Hywan Hywan self-assigned this Dec 17, 2020
In `wasm.h`, `wasm_functype_t` is implemented with
`WASM_DECLARE_TYPE`, so with `WASM_DECLARE_VEC(functype, *)`. This `*`
means the C struct for the vector is defined:

```c
struct wasm_functype_vec_t {
    size_t size;
    wasm_functype_t** data;
}
```

The way we implement `wasm_functype_vec_t` in Rust is with the
`wasm_declare_vec!` macro. And it is wrong. We must use
`wasm_declared_boxed_vec!`.
In `wasm.h`, `wasm_globaltype_t` is implemented with
`WASM_DECLARE_TYPE`, so with `WASM_DECLARE_VEC(globaltype, *)`. This
`*` means the C struct for the vector is defined:

```c
struct wasm_globaltype_vec_t {
    size_t size;
    wasm_globaltype_t** data;
}
```

The way we implement `wasm_globaltype_vec_t` in Rust is with the
`wasm_declare_vec!` macro. And it is wrong. We must use
`wasm_declared_boxed_vec!`.
In `wasm.h`, `wasm_memorytype_t` is implemented with
`WASM_DECLARE_TYPE`, so with `WASM_DECLARE_VEC(memorytype, *)`. This
`*` means the C struct for the vector is defined:

```c
struct wasm_memorytype_vec_t {
    size_t size;
    wasm_memorytype_t** data;
}
```

The way we implement `wasm_memorytype_vec_t` in Rust is with the
`wasm_declare_vec!` macro. And it is wrong. We must use
`wasm_declared_boxed_vec!`.
In `wasm.h`, `wasm_tabletype_t` is implemented with
`WASM_DECLARE_TYPE`, so with `WASM_DECLARE_VEC(tabletype, *)`. This
`*` means the C struct for the vector is defined:

```c
struct wasm_tabletype_vec_t {
    size_t size;
    wasm_tabletype_t** data;
}
```

The way we implement `wasm_tabletype_vec_t` in Rust is with the
`wasm_declare_vec!` macro. And it is wrong. We must use
`wasm_declared_boxed_vec!`.
In `wasm.h`, `wasm_frame_t` is implemented with `WASM_DECLARE_TYPE`,
so with `WASM_DECLARE_VEC(frame, *)`. This `*` means the C struct for
the vector is defined:

```c
struct wasm_frame_vec_t {
    size_t size;
    wasm_frame_t** data;
}
```

The way we implement `wasm_frame_vec_t` in Rust is with the
`wasm_declare_vec!` macro. And it is wrong. We must use
`wasm_declared_boxed_vec!`.
In case of a boxed vector, `wasm_$name_vec_delete` now checks that the
vec is correctly initialized (by checking the first item only) because
transmuting `Vec<*mut T>` to `Vec<Box<T>>`, otherwise it will crash.
@Hywan Hywan changed the title test+doc(c-api) Continue to improve test coverage of the C API test+doc(c-api) Fix vecs that must be boxed vecs + don't transmute uninitialized boxed vec Dec 17, 2020
@Hywan Hywan changed the title test+doc(c-api) Fix vecs that must be boxed vecs + don't transmute uninitialized boxed vec test+doc(c-api) Fix vecs that must be boxed vecs + don't transmute uninitialized boxed vecs Dec 17, 2020
@@ -153,6 +246,27 @@ macro_rules! wasm_declare_boxed_vec {
}
}

impl<'a, T: Into<[<wasm_ $name _t>]> + Clone> From<&'a [T]> for [<wasm_ $name _vec_t>] {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such implementation exists for regular vecs. Now it exists for boxed vecs too.

Comment on lines 350 to 355
// If the vector has been initialized (we check
// only the first item), we can transmute items to
// `Box`es.
if vec.size > 0 && !data[0].is_null() {
let _data: Vec<Box<[<wasm_ $name _t>]>> = ::std::mem::transmute(data);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes allows to delete an uninitialized boxed vec. Should we check for all items or checking the first is safe enough?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For handling uninitialized, I think we just have to make a representation of uninit that makes sense... I think the code as-is will break in real code (for example partially initialized vectors. I'll get back to you with more details after finishing the review and looking at the code locally

@Hywan Hywan marked this pull request as ready for review December 17, 2020 14:07
@Hywan
Copy link
Contributor Author

Hywan commented Dec 17, 2020

bors try

bors bot added a commit that referenced this pull request Dec 17, 2020
@bors
Copy link
Contributor

bors bot commented Dec 17, 2020

try

Build failed:

@Hywan
Copy link
Contributor Author

Hywan commented Dec 17, 2020

Too bad, it's working on aarch64-apple-darwin, but not on x86_64

@Hywan
Copy link
Contributor Author

Hywan commented Dec 17, 2020

Following Vec::with_capacity, we are redirected to RawVec::with_capacity. If we continue the lead, we reach RawVec::with_capacity_in:

https://github.com/rust-lang/rust/blob/e261649593cf9c2707f7b30a61c46c4469c67ebb/library/alloc/src/raw_vec.rs#L128-L130

Notice the difference with RawVec::with_capacity_zeroed_in:

https://github.com/rust-lang/rust/blob/e261649593cf9c2707f7b30a61c46c4469c67ebb/library/alloc/src/raw_vec.rs#L135-L137

Ideally, the wasm_$name_vec_new_uninitialized should zeroed the items, for safety reasons. Otherwise, this change 8aa0822 will not work because we have no guarantee that the items are zeroed. This API is public for RawVec; it is only used by slice::new_zeroed_slice, which is unstable and behind a feature flag.

We could probably just used vec![ptr::null(); len]: yes the vector will allocate memory immediately and everytime, but they are all aimed to be filled one day.

lib/c-api/src/wasm_c_api/macros.rs Outdated Show resolved Hide resolved
lib/c-api/src/wasm_c_api/macros.rs Outdated Show resolved Hide resolved
lib/c-api/src/wasm_c_api/macros.rs Outdated Show resolved Hide resolved
Comment on lines 350 to 355
// If the vector has been initialized (we check
// only the first item), we can transmute items to
// `Box`es.
if vec.size > 0 && !data[0].is_null() {
let _data: Vec<Box<[<wasm_ $name _t>]>> = ::std::mem::transmute(data);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For handling uninitialized, I think we just have to make a representation of uninit that makes sense... I think the code as-is will break in real code (for example partially initialized vectors. I'll get back to you with more details after finishing the review and looking at the code locally

@MarkMcCaskey
Copy link
Contributor

Too bad, it's working on aarch64-apple-darwin, but not on x86_64

How do I reproduce the failure? Locally make test-capi-cranelift-jit is passing on x86_64 macos

…n<&mut T>`.

This was already the case for regular vec. This patch applies the same
pattern for boxed vec.

See wasmerio@deec77d.
It's a safer way to handle partially uninitialized boxed vector, since
it protects against based deletion for every item.
@Hywan
Copy link
Contributor Author

Hywan commented Dec 18, 2020

To handle partially uninitialized boxed vector, I propose to transmute the Vec<*mut T> to Vec<Option<Box<T>>> so that it handles safely all items individually. The corresponding commit is a68a1e6.

This patch updates how `wasm_$name_vec_new_uninitialized` creates the
vector. The vector is now fully allocated with `null` pointer for each
item, instead of having an empty vector with the initial capacity set
to `length`. That way, we are sure the vector is zeroed correctly.
@Hywan
Copy link
Contributor Author

Hywan commented Dec 18, 2020

bors try

bors bot added a commit that referenced this pull request Dec 18, 2020
@bors
Copy link
Contributor

bors bot commented Dec 18, 2020

Copy link
Contributor

@MarkMcCaskey MarkMcCaskey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some other small comments but it overlaps with #1944, and I can change them there!

Comment on lines 76 to 80
wasm_valtype_vec_delete(Some(params.as_mut()));
wasm_valtype_vec_delete(Some(results.as_mut()));

mem::forget(params);
mem::forget(results);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we'd have Drop implemented and it'd do this for us

@Hywan
Copy link
Contributor Author

Hywan commented Dec 18, 2020

bors r+

@bors
Copy link
Contributor

bors bot commented Dec 18, 2020

@bors bors bot merged commit 625e110 into wasmerio:master Dec 18, 2020
bors bot added a commit that referenced this pull request Dec 21, 2020
1953: Get `multi.c` working in the Wasm C API r=MarkMcCaskey a=MarkMcCaskey

Gets the `multi.c` example working.

This PR has significant overlap with #1949 due to changes in vec / boxed vec.

Changes in this PR:
- impl `Clone` more often, implement `*_copy` functions in terms of `Clone`
- clean up boxed vec behavior
- add `*_vec_copy` functions for boxed and unboxed versions
- fix a typo in `wasm-c-api/examples/multi.c`; fixed in the upstream Wasm C API here: WebAssembly/wasm-c-api#163
- additional misc improvements

# Review

- [ ] Add a short description of the the change to the CHANGELOG.md file


Co-authored-by: Mark McCaskey <[email protected]>
Co-authored-by: Mark McCaskey <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working 📚 documentation Do you like to read? 📦 lib-c-api About wasmer-c-api 🧪 tests I love tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants