diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 2d7b30099..27f6fdf31 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3300,13 +3300,17 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { &self.namespace, Flavor::InStruct, ); + // Bind payloads by reference: the payload's heap data must + // stay owned by `op0` (which outlives the lowered call). A + // by-value binding would free that data at the end of the + // `if`/`else` block below, before the flat ABI consumes it. let bind_ok = if let Some(_ok) = result.ok.as_ref() { - format!("{ok_ty} {ok_payload} = std::move({op0}).value();") + format!("{ok_ty}&& {ok_payload} = std::move({op0}).value();") } else { String::new() }; let bind_err = if let Some(_err) = result.err.as_ref() { - format!("{err_ty} {err_payload} = std::move({op0}).error();") + format!("{err_ty}&& {err_payload} = std::move({op0}).error();") } else { String::new() }; diff --git a/crates/test/src/cpp.rs b/crates/test/src/cpp.rs index d33cefa11..436116797 100644 --- a/crates/test/src/cpp.rs +++ b/crates/test/src/cpp.rs @@ -146,8 +146,13 @@ impl LanguageMethods for Cpp { .arg(&bindings_object); runner.run_command(&mut cmd)?; - // Now compile the runner's source code to with the above object and the - // component-type object into a final component. + // Now link the runner's source code with the above object and the + // component-type object into a core module, then componentize with + // the workspace's `wit-component` (mirroring the C harness). This + // skips the WIT processing of the wasi-sdk's bundled + // `wasm-component-ld`, which may predate component-model features + // used by the embedded metadata (e.g. `map`). + let output = compile.output.with_extension("core.wasm"); let mut cmd = Command::new(compiler); cmd.arg(&compile.component.path) .arg(&bindings_object) @@ -170,12 +175,17 @@ impl LanguageMethods for Cpp { .arg("-std=c++20") .arg("-g") .arg("-o") - .arg(&compile.output); + .arg(&output); for flag in Vec::from(config.cflags) { cmd.arg(flag); } cmd.arg("-mexec-model=reactor"); + cmd.arg("-Wl,--skip-wit-component"); runner.run_command(&mut cmd)?; + + runner + .convert_p1_to_component(&output, compile) + .with_context(|| format!("failed to convert {output:?}"))?; Ok(()) } diff --git a/tests/runtime/map/runner.cpp b/tests/runtime/map/runner.cpp new file mode 100644 index 000000000..7d8281c0f --- /dev/null +++ b/tests/runtime/map/runner.cpp @@ -0,0 +1,229 @@ +//@ wasmtime-flags = '-Wcomponent-model-map' + +#include +#include + +#include +#include +#include + +namespace to_test = ::test::maps::to_test; + +using wit::string; +using wit::unordered_map; + +static bool str_eq(string const& a, std::string_view b) { return a.get_view() == b; } + +template +static V const* find(unordered_map const& m, std::string_view key) { + for (auto const& [k, v] : m) { + if (k.get_view() == key) { + return &v; + } + } + return nullptr; +} + +template +static V const* find(unordered_map const& m, uint32_t key) { + for (auto const& [k, v] : m) { + if (k == key) { + return &v; + } + } + return nullptr; +} + +static unordered_map make_names( + std::initializer_list> entries) { + auto result = unordered_map::allocate(entries.size()); + size_t i = 0; + for (auto const& [key, value] : entries) { + result.initialize(i++, std::make_pair(key, string::from_view(value))); + } + return result; +} + +static void test_named_roundtrip() { + std::vector> input{{1, "uno"}, {2, "two"}}; + auto result = to_test::NamedRoundtrip(input); + assert(result.size() == 2); + auto uno = find(result, "uno"); + assert(uno && *uno == 1); + auto two = find(result, "two"); + assert(two && *two == 2); +} + +static void test_bytes_roundtrip() { + uint8_t const world_bytes[] = {'w', 'o', 'r', 'l', 'd'}; + uint8_t const bin_bytes[] = {0, 1, 2}; + std::vector>> input{ + {"hello", world_bytes}, + {"bin", bin_bytes}, + }; + auto result = to_test::BytesRoundtrip(input); + assert(result.size() == 2); + auto hello = find(result, "hello"); + assert(hello && hello->size() == 5); + assert(memcmp(hello->data(), "world", 5) == 0); + auto bin = find(result, "bin"); + assert(bin && bin->size() == 3); + assert((*bin)[0] == 0 && (*bin)[1] == 1 && (*bin)[2] == 2); +} + +static void test_empty_roundtrip() { + auto result = to_test::EmptyRoundtrip({}); + assert(result.empty()); +} + +static void test_option_roundtrip() { + std::vector>> input{ + {"some", 42}, + {"none", std::nullopt}, + }; + auto result = to_test::OptionRoundtrip(input); + assert(result.size() == 2); + auto some = find(result, "some"); + assert(some && some->has_value() && **some == 42); + auto none = find(result, "none"); + assert(none && !none->has_value()); +} + +static void test_record_roundtrip() { + to_test::LabeledEntry input{ + string::from_view("test-label"), + make_names({{10, "ten"}, {20, "twenty"}}), + }; + auto result = to_test::RecordRoundtrip(std::move(input)); + assert(str_eq(result.label, "test-label")); + assert(result.values.size() == 2); + auto ten = find(result.values, 10); + assert(ten && str_eq(*ten, "ten")); + auto twenty = find(result.values, 20); + assert(twenty && str_eq(*twenty, "twenty")); +} + +static void test_inline_roundtrip() { + std::vector> input{{1, "one"}, {2, "two"}}; + auto result = to_test::InlineRoundtrip(input); + assert(result.size() == 2); + auto one = find(result, "one"); + assert(one && *one == 1); + auto two = find(result, "two"); + assert(two && *two == 2); +} + +static void test_large_roundtrip() { + size_t const n = 100; + std::vector storage; + storage.reserve(n); + std::vector> input; + input.reserve(n); + for (size_t i = 0; i < n; i++) { + storage.push_back("value-" + std::to_string(i)); + input.emplace_back(uint32_t(i), storage.back()); + } + auto result = to_test::LargeRoundtrip(input); + assert(result.size() == n); + auto value = find(result, 42); + assert(value && str_eq(*value, "value-42")); +} + +static void test_multi_param_roundtrip() { + std::vector> names{{1, "one"}, {2, "two"}}; + uint8_t const payload[] = {42}; + std::vector>> bytes{{"key", payload}}; + auto [ids, bytes_out] = to_test::MultiParamRoundtrip(names, bytes); + assert(ids.size() == 2); + auto one = find(ids, "one"); + assert(one && *one == 1); + auto two = find(ids, "two"); + assert(two && *two == 2); + assert(bytes_out.size() == 1); + auto key = find(bytes_out, "key"); + assert(key && key->size() == 1 && (*key)[0] == 42); +} + +static void test_nested_roundtrip() { + std::vector> inner_a{{1, "one"}, {2, "two"}}; + std::vector> inner_b{{10, "ten"}}; + std::vector const>>> + outer{ + {"group-a", inner_a}, + {"group-b", inner_b}, + }; + auto result = to_test::NestedRoundtrip(outer); + assert(result.size() == 2); + auto group_a = find(result, "group-a"); + assert(group_a && group_a->size() == 2); + auto two = find(*group_a, 2); + assert(two && str_eq(*two, "two")); + auto group_b = find(result, "group-b"); + assert(group_b && group_b->size() == 1); + auto ten = find(*group_b, 10); + assert(ten && str_eq(*ten, "ten")); +} + +static void test_variant_roundtrip() { + to_test::MapOrString map_input{to_test::MapOrString::AsMap{make_names({{1, "one"}})}}; + auto map_result = to_test::VariantRoundtrip(std::move(map_input)); + auto* as_map = std::get_if(&map_result.variants); + assert(as_map && as_map->value.size() == 1); + auto one = find(as_map->value, 1); + assert(one && str_eq(*one, "one")); + + to_test::MapOrString string_input{to_test::MapOrString::AsString{string::from_view("hello")}}; + auto string_result = to_test::VariantRoundtrip(std::move(string_input)); + auto* as_string = std::get_if(&string_result.variants); + assert(as_string && str_eq(as_string->value, "hello")); +} + +static void test_result_roundtrip() { + std::expected, string> ok_input{make_names({{5, "five"}})}; + auto ok_result = to_test::ResultRoundtrip(std::move(ok_input)); + assert(ok_result.has_value()); + assert(ok_result->size() == 1); + auto five = find(*ok_result, 5); + assert(five && str_eq(*five, "five")); + + std::expected, string> err_input{ + std::unexpected(string::from_view("bad input"))}; + auto err_result = to_test::ResultRoundtrip(std::move(err_input)); + assert(!err_result.has_value()); + assert(str_eq(err_result.error(), "bad input")); +} + +static void test_tuple_roundtrip() { + std::vector> entries{{7, "seven"}}; + auto result = to_test::TupleRoundtrip(std::make_tuple( + std::span const>(entries), uint64_t(42))); + auto& [values, number] = result; + assert(values.size() == 1); + auto seven = find(values, 7); + assert(seven && str_eq(*seven, "seven")); + assert(number == 42); +} + +static void test_single_entry_roundtrip() { + std::vector> input{{99, "ninety-nine"}}; + auto result = to_test::SingleEntryRoundtrip(input); + assert(result.size() == 1); + auto value = find(result, 99); + assert(value && str_eq(*value, "ninety-nine")); +} + +void exports::runner::Run() { + test_named_roundtrip(); + test_bytes_roundtrip(); + test_empty_roundtrip(); + test_option_roundtrip(); + test_record_roundtrip(); + test_inline_roundtrip(); + test_large_roundtrip(); + test_multi_param_roundtrip(); + test_nested_roundtrip(); + test_variant_roundtrip(); + test_result_roundtrip(); + test_tuple_roundtrip(); + test_single_entry_roundtrip(); +} diff --git a/tests/runtime/map/test.cpp b/tests/runtime/map/test.cpp new file mode 100644 index 000000000..f06f781b6 --- /dev/null +++ b/tests/runtime/map/test.cpp @@ -0,0 +1,76 @@ +#include + +namespace test_exports = ::exports::test::maps::to_test; + +using wit::string; +using wit::unordered_map; +using wit::vector; + +static unordered_map invert(unordered_map a) { + auto result = unordered_map::allocate(a.size()); + size_t i = 0; + for (auto& [id, name] : a) { + result.initialize(i++, std::make_pair(std::move(name), id)); + } + return result; +} + +unordered_map test_exports::NamedRoundtrip(unordered_map a) { + return invert(std::move(a)); +} + +unordered_map> test_exports::BytesRoundtrip( + unordered_map> a) { + return a; +} + +unordered_map test_exports::EmptyRoundtrip(unordered_map a) { + return a; +} + +unordered_map> test_exports::OptionRoundtrip( + unordered_map> a) { + return a; +} + +test_exports::LabeledEntry test_exports::RecordRoundtrip(test_exports::LabeledEntry a) { + return a; +} + +unordered_map test_exports::InlineRoundtrip(unordered_map a) { + return invert(std::move(a)); +} + +unordered_map test_exports::LargeRoundtrip(unordered_map a) { + return a; +} + +std::tuple, unordered_map>> +test_exports::MultiParamRoundtrip(unordered_map a, + unordered_map> b) { + return std::make_tuple(invert(std::move(a)), std::move(b)); +} + +unordered_map> test_exports::NestedRoundtrip( + unordered_map> a) { + return a; +} + +test_exports::MapOrString test_exports::VariantRoundtrip(test_exports::MapOrString a) { + return a; +} + +std::expected, string> test_exports::ResultRoundtrip( + std::expected, string> a) { + return a; +} + +std::tuple, uint64_t> test_exports::TupleRoundtrip( + std::tuple, uint64_t> a) { + return a; +} + +unordered_map test_exports::SingleEntryRoundtrip( + unordered_map a) { + return a; +}