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
8 changes: 6 additions & 2 deletions crates/cpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
};
Expand Down
16 changes: 13 additions & 3 deletions crates/test/src/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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(())
}

Expand Down
229 changes: 229 additions & 0 deletions tests/runtime/map/runner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
//@ wasmtime-flags = '-Wcomponent-model-map'

#include <assert.h>
#include <runner_cpp.h>

#include <cstring>
#include <string>
#include <vector>

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 <class V>
static V const* find(unordered_map<string, V> const& m, std::string_view key) {
for (auto const& [k, v] : m) {
if (k.get_view() == key) {
return &v;
}
}
return nullptr;
}

template <class V>
static V const* find(unordered_map<uint32_t, V> const& m, uint32_t key) {
for (auto const& [k, v] : m) {
if (k == key) {
return &v;
}
}
return nullptr;
}

static unordered_map<uint32_t, string> make_names(
std::initializer_list<std::pair<uint32_t, std::string_view>> entries) {
auto result = unordered_map<uint32_t, string>::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<std::pair<uint32_t, std::string_view>> 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<std::pair<std::string_view, std::span<uint8_t const>>> 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<std::pair<std::string_view, std::optional<uint32_t>>> 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<std::pair<uint32_t, std::string_view>> 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<std::string> storage;
storage.reserve(n);
std::vector<std::pair<uint32_t, std::string_view>> 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<std::pair<uint32_t, std::string_view>> names{{1, "one"}, {2, "two"}};
uint8_t const payload[] = {42};
std::vector<std::pair<std::string_view, std::span<uint8_t const>>> 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<std::pair<uint32_t, std::string_view>> inner_a{{1, "one"}, {2, "two"}};
std::vector<std::pair<uint32_t, std::string_view>> inner_b{{10, "ten"}};
std::vector<std::pair<std::string_view, std::span<std::pair<uint32_t, std::string_view> 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<to_test::MapOrString::AsMap>(&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<to_test::MapOrString::AsString>(&string_result.variants);
assert(as_string && str_eq(as_string->value, "hello"));
}

static void test_result_roundtrip() {
std::expected<unordered_map<uint32_t, string>, 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<unordered_map<uint32_t, string>, 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<std::pair<uint32_t, std::string_view>> entries{{7, "seven"}};
auto result = to_test::TupleRoundtrip(std::make_tuple(
std::span<std::pair<uint32_t, std::string_view> 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<std::pair<uint32_t, std::string_view>> 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();
}
76 changes: 76 additions & 0 deletions tests/runtime/map/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include <test_cpp.h>

namespace test_exports = ::exports::test::maps::to_test;

using wit::string;
using wit::unordered_map;
using wit::vector;

static unordered_map<string, uint32_t> invert(unordered_map<uint32_t, string> a) {
auto result = unordered_map<string, uint32_t>::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<string, uint32_t> test_exports::NamedRoundtrip(unordered_map<uint32_t, string> a) {
return invert(std::move(a));
}

unordered_map<string, vector<uint8_t>> test_exports::BytesRoundtrip(
unordered_map<string, vector<uint8_t>> a) {
return a;
}

unordered_map<uint32_t, string> test_exports::EmptyRoundtrip(unordered_map<uint32_t, string> a) {
return a;
}

unordered_map<string, std::optional<uint32_t>> test_exports::OptionRoundtrip(
unordered_map<string, std::optional<uint32_t>> a) {
return a;
}

test_exports::LabeledEntry test_exports::RecordRoundtrip(test_exports::LabeledEntry a) {
return a;
}

unordered_map<string, uint32_t> test_exports::InlineRoundtrip(unordered_map<uint32_t, string> a) {
return invert(std::move(a));
}

unordered_map<uint32_t, string> test_exports::LargeRoundtrip(unordered_map<uint32_t, string> a) {
return a;
}

std::tuple<unordered_map<string, uint32_t>, unordered_map<string, vector<uint8_t>>>
test_exports::MultiParamRoundtrip(unordered_map<uint32_t, string> a,
unordered_map<string, vector<uint8_t>> b) {
return std::make_tuple(invert(std::move(a)), std::move(b));
}

unordered_map<string, unordered_map<uint32_t, string>> test_exports::NestedRoundtrip(
unordered_map<string, unordered_map<uint32_t, string>> a) {
return a;
}

test_exports::MapOrString test_exports::VariantRoundtrip(test_exports::MapOrString a) {
return a;
}

std::expected<unordered_map<uint32_t, string>, string> test_exports::ResultRoundtrip(
std::expected<unordered_map<uint32_t, string>, string> a) {
return a;
}

std::tuple<unordered_map<uint32_t, string>, uint64_t> test_exports::TupleRoundtrip(
std::tuple<unordered_map<uint32_t, string>, uint64_t> a) {
return a;
}

unordered_map<uint32_t, string> test_exports::SingleEntryRoundtrip(
unordered_map<uint32_t, string> a) {
return a;
}
Loading