Skip to content
Open
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
13 changes: 11 additions & 2 deletions crates/core/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2404,7 +2404,13 @@ impl<'a, B: Bindgen> Generator<'a, B> {
TypeDefKind::Resource => unreachable!(),
TypeDefKind::Unknown => unreachable!(),

TypeDefKind::FixedLengthList(..) => todo!(),
TypeDefKind::FixedLengthList(element, size) => {
self.flat_for_each_record_type(
ty,
iter::repeat_n(element, *size as usize),
|me, ty| me.deallocate(ty, what),
);
}
TypeDefKind::Map(..) => todo!(),
},
}
Expand Down Expand Up @@ -2526,7 +2532,10 @@ impl<'a, B: Bindgen> Generator<'a, B> {
TypeDefKind::Future(_) => unreachable!(),
TypeDefKind::Stream(_) => unreachable!(),
TypeDefKind::Unknown => unreachable!(),
TypeDefKind::FixedLengthList(_, _) => {}
TypeDefKind::FixedLengthList(element, size) => {
let tys: Vec<Type> = iter::repeat_n(*element, *size as usize).collect();
self.deallocate_indirect_fields(&tys, addr, offset, what);
}
TypeDefKind::Map(..) => todo!(),
},
}
Expand Down
38 changes: 38 additions & 0 deletions crates/rust/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,44 @@ mod newtyped_list {
}
}
}

#[test]
fn fixed_length_list_deallocation() {
let wit = r#"
package test:fll-dealloc;

world test {
export return-string-array: func() -> list<string, 3>;
export accept-string-array: func(a: list<string, 3>);
}
"#;

let mut resolve = wit_bindgen_core::wit_parser::Resolve::default();
let pkg = resolve.push_str("test.wit", wit).unwrap();
let world = resolve.select_world(&[pkg], None).unwrap();

let opts = wit_bindgen_rust::Opts {
generate_all: true,
..Default::default()
};

let mut generator = opts.build();
let mut files = wit_bindgen_core::Files::default();
use wit_bindgen_core::WorldGenerator;
generator.generate(&mut resolve, world, &mut files).unwrap();

let (_name, contents) = files.iter().next().unwrap();
let code = String::from_utf8_lossy(contents);

// Verify post-return deallocation functions are generated for
// fixed-length lists containing heap-allocated elements (strings)
assert!(
code.contains("cabi_post_"),
"expected cabi_post_ deallocation function for fixed-length list \
with string elements, but none was generated"
);
}

#[allow(unused, reason = "testing codegen, not functionality")]
mod retyped_list {
use std::ops::Deref;
Expand Down
30 changes: 30 additions & 0 deletions tests/runtime/fixed-length-lists/alloc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};

#[global_allocator]
static ALLOC: A = A;

static ALLOC_AMT: AtomicUsize = AtomicUsize::new(0);

struct A;

unsafe impl GlobalAlloc for A {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ptr = System.alloc(layout);
if !ptr.is_null() {
ALLOC_AMT.fetch_add(layout.size(), SeqCst);
}
return ptr;
}

unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
// Poison all deallocations to try to catch any use-after-free in the
// bindings as early as possible.
std::ptr::write_bytes(ptr, 0xde, layout.size());
ALLOC_AMT.fetch_sub(layout.size(), SeqCst);
System.dealloc(ptr, layout)
}
}
pub fn get() -> usize {
ALLOC_AMT.load(SeqCst)
}
46 changes: 46 additions & 0 deletions tests/runtime/fixed-length-lists/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ include!(env!("BINDINGS"));

use test::fixed_length_lists::to_test::*;

mod alloc;

struct Guard {
me_before: usize,
remote_before: u32,
}

impl Guard {
fn new() -> Guard {
Guard {
me_before: alloc::get(),
remote_before: allocated_bytes(),
}
}
}

impl Drop for Guard {
fn drop(&mut self) {
assert_eq!(self.me_before, alloc::get());
assert_eq!(self.remote_before, allocated_bytes());
}
}

struct Component;

export!(Component);
Expand Down Expand Up @@ -70,5 +93,28 @@ impl Guest for Component {
assert_eq!(result[0].l, [1, -1]);
assert_eq!(result[1].l, [2, -2]);
}

// Test deallocation of heap-allocated elements in fixed-length lists.
// Strings require heap allocation, so passing and returning them through
// fixed-length lists exercises the deallocate/deallocate_indirect paths.
{
let _guard = Guard::new();
string_list_param([
"hello".to_string(),
"world".to_string(),
"!".to_string(),
]);
}
{
let _guard = Guard::new();
let result = string_list_result();
assert_eq!(result, ["foo", "bar"]);
}
{
let _guard = Guard::new();
let result =
string_list_roundtrip(["hello".to_string(), "world".to_string()]);
assert_eq!(result, ["hello", "world"]);
}
}
}
14 changes: 14 additions & 0 deletions tests/runtime/fixed-length-lists/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,17 @@ std::array<to_test::Nested, 2>
to_test::NightmareOnCpp(std::array<to_test::Nested, 2> a) {
return a;
}
void to_test::StringListParam(std::array<wit::string, 3> a) {
assert(a[0].get_view() == "hello");
assert(a[1].get_view() == "world");
assert(a[2].get_view() == "!");
}
std::array<wit::string, 2> to_test::StringListResult() {
return {wit::string::from_view("foo"), wit::string::from_view("bar")};
}
std::array<wit::string, 2> to_test::StringListRoundtrip(std::array<wit::string, 2> a) {
return a;
}
uint32_t to_test::AllocatedBytes() {
return 0;
}
16 changes: 16 additions & 0 deletions tests/runtime/fixed-length-lists/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ struct Component;

export!(Component);

mod alloc;

use crate::exports::test::fixed_length_lists::to_test::Nested;

impl exports::test::fixed_length_lists::to_test::Guest for Component {
fn allocated_bytes() -> u32 {
alloc::get().try_into().unwrap()
}
fn list_param(a: [u32; 4]) {
assert_eq!(a, [1, 2, 3, 4]);
}
Expand Down Expand Up @@ -40,4 +45,15 @@ impl exports::test::fixed_length_lists::to_test::Guest for Component {
fn nightmare_on_cpp(a: [Nested; 2]) -> [Nested; 2] {
a
}
fn string_list_param(a: [String; 3]) {
assert_eq!(a[0], "hello");
assert_eq!(a[1], "world");
assert_eq!(a[2], "!");
}
fn string_list_result() -> [String; 2] {
["foo".to_string(), "bar".to_string()]
}
fn string_list_roundtrip(a: [String; 2]) -> [String; 2] {
a
}
}
8 changes: 8 additions & 0 deletions tests/runtime/fixed-length-lists/test.wit
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ interface to-test {
}

nightmare-on-cpp: func(a: list<nested, 2>) -> list<nested, 2>;

/// Functions that test deallocation of heap-allocated elements
/// within fixed-length lists (e.g. strings require deallocation).
string-list-param: func(a: list<string, 3>);
string-list-result: func() -> list<string, 2>;
string-list-roundtrip: func(a: list<string, 2>) -> list<string, 2>;

allocated-bytes: func() -> u32;
}

world test {
Expand Down
Loading