From 19f259b191537719881c430ff6c5cfda933d653b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 7 Jan 2025 13:08:13 -0600 Subject: [PATCH] Fix mismatched tuple ABIs in Rust (#1113) * Fix mismatched tuple ABIs in Rust This fixes the mistaken assumption that the tuple ABI in Rust is the same as the component model ABI and generates different code for lifting/lowering lists. Closes #1112 * Attempt a java test * Add a go test * Fix java test * Attempt to write C# --- crates/core/src/types.rs | 5 ++++ crates/rust/src/bindgen.rs | 7 +++++- tests/runtime/lists.rs | 4 +++ tests/runtime/lists/wasm.c | 25 +++++++++++++++++++ tests/runtime/lists/wasm.cs | 21 ++++++++++++---- tests/runtime/lists/wasm.go | 12 +++++++++ tests/runtime/lists/wasm.rs | 5 ++++ .../wit_exports_test_lists_TestImpl.java | 11 ++++++++ tests/runtime/lists/world.wit | 1 + 9 files changed, 85 insertions(+), 6 deletions(-) diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index a0862f5e8..5aaa71607 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -30,6 +30,9 @@ pub struct TypeInfo { /// Whether this type (transitively) has a list (or string). pub has_list: bool, + /// Whether this type (transitively) has a tuple. + pub has_tuple: bool, + /// Whether this type (transitively) has a resource (or handle). pub has_resource: bool, @@ -46,6 +49,7 @@ impl std::ops::BitOrAssign for TypeInfo { self.owned |= rhs.owned; self.error |= rhs.error; self.has_list |= rhs.has_list; + self.has_tuple |= rhs.has_tuple; self.has_resource |= rhs.has_resource; self.has_borrow_handle |= rhs.has_borrow_handle; self.has_own_handle |= rhs.has_own_handle; @@ -171,6 +175,7 @@ impl Types { for ty in t.types.iter() { info |= self.type_info(resolve, ty); } + info.has_tuple = true; } TypeDefKind::Flags(_) => {} TypeDefKind::Enum(_) => {} diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 1e9dc9ff1..e0b21f4dc 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -292,7 +292,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { return false; } match ty { - Type::Id(id) => !self.gen.gen.types.get(*id).has_resource, + // Note that tuples in Rust are not ABI-compatible with component + // model tuples, so those are exempted here from canonical lists. + Type::Id(id) => { + let info = self.gen.gen.types.get(*id); + !info.has_resource && !info.has_tuple + } _ => true, } } diff --git a/tests/runtime/lists.rs b/tests/runtime/lists.rs index 114ff1724..f5761ccfc 100644 --- a/tests/runtime/lists.rs +++ b/tests/runtime/lists.rs @@ -47,6 +47,10 @@ impl test::lists::test::Host for MyImports { assert_eq!(ptr[1][0], "baz"); } + fn list_param5(&mut self, ptr: Vec<(u8, u32, u8)>) { + assert_eq!(ptr, [(1, 2, 3), (4, 5, 6)]); + } + fn list_result(&mut self) -> Vec { vec![1, 2, 3, 4, 5] } diff --git a/tests/runtime/lists/wasm.c b/tests/runtime/lists/wasm.c index 253dc586d..7787984a1 100644 --- a/tests/runtime/lists/wasm.c +++ b/tests/runtime/lists/wasm.c @@ -78,6 +78,20 @@ void exports_lists_test_imports() { test_lists_test_list_param4(&a); } + { + lists_tuple3_u8_u32_u8_t data[2]; + data[0].f0 = 1; + data[0].f1 = 2; + data[0].f2 = 3; + data[1].f0 = 4; + data[1].f1 = 5; + data[1].f2 = 6; + lists_list_tuple3_u8_u32_u8_t a; + a.len = 2; + a.ptr = data; + test_lists_test_list_param5(&a); + } + { lists_list_u8_t a; test_lists_test_list_result(&a); @@ -301,6 +315,17 @@ void exports_test_lists_test_list_param4(lists_list_list_string_t *a) { lists_list_list_string_free(a); } +void exports_test_lists_test_list_param5(lists_list_tuple3_u8_u32_u8_t *a) { + assert(a->len == 2); + assert(a->ptr[0].f0 == 1); + assert(a->ptr[0].f1 == 2); + assert(a->ptr[0].f2 == 3); + assert(a->ptr[1].f0 == 4); + assert(a->ptr[1].f1 == 5); + assert(a->ptr[1].f2 == 6); + lists_list_tuple3_u8_u32_u8_free(a); +} + void exports_test_lists_test_list_result(lists_list_u8_t *ret0) { ret0->ptr = (uint8_t *) malloc(5); ret0->len = 5; diff --git a/tests/runtime/lists/wasm.cs b/tests/runtime/lists/wasm.cs index adefc9577..fd929c7ba 100644 --- a/tests/runtime/lists/wasm.cs +++ b/tests/runtime/lists/wasm.cs @@ -18,7 +18,7 @@ public static void TestImports() TestInterop.EmptyListParam(new byte[0]); TestInterop.EmptyStringParam(""); - + { byte[] result = TestInterop.EmptyListResult(); Debug.Assert(result.Length == 0); @@ -62,7 +62,7 @@ public static void TestImports() Console.WriteLine(result); Debug.Assert(result == "hello!"); } - + { List result = TestInterop.ListResult3(); Debug.Assert(result.Count() == 2); @@ -103,7 +103,7 @@ public static void TestImports() Debug.Assert(u.Length == 2, $"u.Length {u.Length}"); Debug.Assert(u[0] == ushort.MinValue, $"u[0] == {u[0]}"); Debug.Assert(u[1] == ushort.MaxValue, $"u[1] == {u[1]}"); - + Debug.Assert(s.Length == 2); Console.WriteLine(s[0]); Console.WriteLine(s[1]); @@ -112,7 +112,7 @@ public static void TestImports() { var (u, s) = TestInterop.ListMinmax32( - new uint[] { uint.MinValue, uint.MaxValue }, + new uint[] { uint.MinValue, uint.MaxValue }, new int[] { int.MinValue, int.MaxValue } ); @@ -130,7 +130,7 @@ public static void TestImports() Debug.Assert(s.Length == 2 && s[0] == long.MinValue && s[1] == long.MaxValue); } - + { var (u, s) = TestInterop.ListMinmaxFloat( new float[] { @@ -220,6 +220,17 @@ public static void ListParam4(List> a) Debug.Assert(a[1][0].Equals("baz")); } + public static void ListParam5(List<(byte, uint, byte)> a) + { + Debug.Assert(a.Count() == 2); + Debug.Assert(a[0].Item1 == 1); + Debug.Assert(a[0].Item2 == 2); + Debug.Assert(a[0].Item3 == 3); + Debug.Assert(a[1].Item1 == 4); + Debug.Assert(a[1].Item2 == 5); + Debug.Assert(a[1].Item3 == 6); + } + public static byte[] ListResult() { return new byte[] { (byte)1, (byte)2, (byte)3, (byte)4, (byte)5 }; diff --git a/tests/runtime/lists/wasm.go b/tests/runtime/lists/wasm.go index d4f96a15b..119b3d0f2 100644 --- a/tests/runtime/lists/wasm.go +++ b/tests/runtime/lists/wasm.go @@ -200,6 +200,18 @@ func (i ListImpl) ListParam4(a [][]string) { } } +func (i ListImpl) ListParam5(a []ExportsTestListsTestTuple3U8U32U8T) { + if len(a) != 2 { + panic("ListParam5") + } + if a[0].F0 != 1 || a[0].F1 != 2 || a[0].F2 != 3 { + panic("ListParam5") + } + if a[1].F0 != 4 || a[1].F1 != 5 || a[1].F2 != 6 { + panic("ListParam5") + } +} + func (i ListImpl) ListResult() []uint8 { return []uint8{1, 2, 3, 4, 5} } diff --git a/tests/runtime/lists/wasm.rs b/tests/runtime/lists/wasm.rs index 8107eab29..d13eb5c58 100644 --- a/tests/runtime/lists/wasm.rs +++ b/tests/runtime/lists/wasm.rs @@ -28,6 +28,7 @@ impl Guest for Component { vec!["foo".to_owned(), "bar".to_owned()], vec!["baz".to_owned()], ]); + list_param5(&[(1, 2, 3), (4, 5, 6)]); assert_eq!(list_result(), [1, 2, 3, 4, 5]); assert_eq!(list_result2(), "hello!"); assert_eq!(list_result3(), ["hello,", "world!"]); @@ -109,6 +110,10 @@ impl exports::test::lists::test::Guest for Component { assert_eq!(ptr[1][0], "baz"); } + fn list_param5(ptr: Vec<(u8, u32, u8)>) { + assert_eq!(ptr, [(1, 2, 3), (4, 5, 6)]); + } + fn list_result() -> Vec { vec![1, 2, 3, 4, 5] } diff --git a/tests/runtime/lists/wit_exports_test_lists_TestImpl.java b/tests/runtime/lists/wit_exports_test_lists_TestImpl.java index 492760aa8..58ad90677 100644 --- a/tests/runtime/lists/wit_exports_test_lists_TestImpl.java +++ b/tests/runtime/lists/wit_exports_test_lists_TestImpl.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import wit.worlds.Lists.Tuple2; +import wit.worlds.Lists.Tuple3; public class TestImpl { public static void emptyListParam(byte[] a) { @@ -52,6 +53,16 @@ public static void listParam4(ArrayList> a) { expect(a.get(1).get(0).equals("baz")); } + public static void listParam5(ArrayList> a) { + expect(a.size() == 2); + expect(a.get(0).f0 == 1); + expect(a.get(0).f1 == 2); + expect(a.get(0).f2 == 3); + expect(a.get(1).f0 == 4); + expect(a.get(1).f1 == 5); + expect(a.get(1).f2 == 6); + } + public static byte[] listResult() { return new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5 }; } diff --git a/tests/runtime/lists/world.wit b/tests/runtime/lists/world.wit index 3be347a41..4e5730875 100644 --- a/tests/runtime/lists/world.wit +++ b/tests/runtime/lists/world.wit @@ -10,6 +10,7 @@ interface test { list-param2: func(a: string); list-param3: func(a: list); list-param4: func(a: list>); + list-param5: func(a: list>); list-result: func() -> list; list-result2: func() -> string; list-result3: func() -> list;