Stream: wamr

Topic: Component Model (partial ABI implementation)


view this post on Zulip Gordon Smith (May 06 2025 at 17:14):

If anyone is lurking, I now have this slimmed down to the following pattern:

auto call_and = attach<func_t<bool_t(bool_t, bool_t)>>(module_inst, exec_env, liftLowerContext, "example:sample/booleans#and");
std::cout << "call_and(false, false): " << call_and(false, false) << std::endl;
std::cout << "call_and(false, true): " << call_and(false, true) << std::endl;
std::cout << "call_and(true, false): " << call_and(true, false) << std::endl;
std::cout << "call_and(true, true): " << call_and(true, true) << std::endl;

auto call_add = attach<func_t<float64_t(float64_t, float64_t)>>(module_inst, exec_env, liftLowerContext, "example:sample/floats#add");
std::cout << "call_add(3.1, 0.2): " << call_add(3.1, 0.2) << std::endl;
std::cout << "call_add(1.5, 2.5): " << call_add(1.5, 2.5) << std::endl;

auto call_reverse = attach<func_t<string_t(string_t)>>(module_inst, exec_env, liftLowerContext, "example:sample/strings#reverse");
auto call_reverse_result = call_reverse("Hello World!");
std::cout << "call_reverse(\"Hello World!\"): " << call_reverse_result << std::endl;
std::cout << "call_reverse(call_reverse(\"Hello World!\")): " << call_reverse(call_reverse_result) << std::endl;

auto call_lots = attach<func_t<uint32_t(
    string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t,
    string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t)>>(module_inst, exec_env, liftLowerContext, "example:sample/strings#lots");

// Example call with 33 string_t arguments
auto call_lots_result = call_lots(
    "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8",
    "p9", "p10", "p11", "p12", "p13", "p14", "p15", "p16", "p17");
std::cout << "call_lots result: " << call_lots_result << std::endl;

using reverse_tuple_func_t = func_t<tuple_t<string_t, bool_t>(tuple_t<bool_t, string_t>)>;
auto call_reverse_tuple = attach<reverse_tuple_func_t>(module_inst, exec_env, liftLowerContext, "example:sample/tuples#reverse");
auto call_reverse_tuple_result = call_reverse_tuple({false, "Hello World!"});
std::cout << "call_reverse_tuple({false, \"Hello World!\"}): " << std::get<0>(call_reverse_tuple_result) << ", " << std::get<1>(call_reverse_tuple_result) << std::endl;

auto call_list_filter = attach<func_t<list_t<string_t>(list_t<variant_t<bool_t, string_t>>)>>(module_inst, exec_env, liftLowerContext, "example:sample/lists#filter-bool");

auto call_list_filter_result = call_list_filter({{false}, {"Hello World!"}, {"Another String"}, {true}});
std::cout << "call_list_filter result: " << call_list_filter_result.size() << std::endl;

view this post on Zulip Gordon Smith (May 06 2025 at 17:17):

Next step is to remove the need for the func_t

view this post on Zulip Gordon Smith (May 06 2025 at 17:47):

auto call_and = attach<bool_t(bool_t, bool_t)>(module_inst, exec_env, liftLowerContext, "example:sample/booleans#and");
std::cout << "call_and(false, false): " << call_and(false, false) << std::endl;
std::cout << "call_and(false, true): " << call_and(false, true) << std::endl;
std::cout << "call_and(true, false): " << call_and(true, false) << std::endl;
std::cout << "call_and(true, true): " << call_and(true, true) << std::endl;

auto call_add = attach<float64_t(float64_t, float64_t)>(module_inst, exec_env, liftLowerContext, "example:sample/floats#add");
std::cout << "call_add(3.1, 0.2): " << call_add(3.1, 0.2) << std::endl;
std::cout << "call_add(1.5, 2.5): " << call_add(1.5, 2.5) << std::endl;

auto call_reverse = attach<string_t(string_t)>(module_inst, exec_env, liftLowerContext, "example:sample/strings#reverse");
auto call_reverse_result = call_reverse("Hello World!");
std::cout << "call_reverse(\"Hello World!\"): " << call_reverse_result << std::endl;
std::cout << "call_reverse(call_reverse(\"Hello World!\")): " << call_reverse(call_reverse_result) << std::endl;

auto call_lots = attach<uint32_t(
    string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t,
    string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t)>(module_inst, exec_env, liftLowerContext, "example:sample/strings#lots");

// Example call with 33 string_t arguments
auto call_lots_result = call_lots(
    "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8",
    "p9", "p10", "p11", "p12", "p13", "p14", "p15", "p16", "p17");
std::cout << "call_lots result: " << call_lots_result << std::endl;

using reverse_tuple_func_t = tuple_t<string_t, bool_t>(tuple_t<bool_t, string_t>);
auto call_reverse_tuple = attach<reverse_tuple_func_t>(module_inst, exec_env, liftLowerContext, "example:sample/tuples#reverse");
auto call_reverse_tuple_result = call_reverse_tuple({false, "Hello World!"});
std::cout << "call_reverse_tuple({false, \"Hello World!\"}): " << std::get<0>(call_reverse_tuple_result) << ", " << std::get<1>(call_reverse_tuple_result) << std::endl;

auto call_list_filter = attach<list_t<string_t>(list_t<variant_t<bool_t, string_t>>)>(module_inst, exec_env, liftLowerContext, "example:sample/lists#filter-bool");

auto call_list_filter_result = call_list_filter({{false}, {"Hello World!"}, {"Another String"}, {true}});
std::cout << "call_list_filter result: " << call_list_filter_result.size() << std::endl;

view this post on Zulip Gordon Smith (May 27 2025 at 12:56):

...First draft of host functions, now working:

string_t reverse(const string_t &a)
{
    std::string result = a;
    std::transform(result.begin(), result.end(), result.begin(), ::toupper);
    return result;
}
uint32_t lots(const string_t &p1, const string_t &p2, const string_t &p3, const string_t &p4, const string_t &p5, const string_t &p6, const string_t &p7, const string_t &p8, const string_t &p9, const string_t &p10, const string_t &p11, const string_t &p12, const string_t &p13, const string_t &p14, const string_t &p15, const string_t &p16, const string_t &p17)
{
    return p1.length() + p2.length() + p3.length() + p4.length() + p5.length() + p6.length() + p7.length() + p8.length() + p9.length() + p10.length() + p11.length() + p12.length() + p13.length() + p14.length() + p15.length() + p16.length() + p17.length();
}
NativeSymbol strings_symbol[] = {
    host_function("reverse", reverse),
    host_function("lots", lots),
};

void_t log_u32(uint32_t a, string_t b)
{
    std::cout << "wasm-log:  " << b << a << std::endl;
}
NativeSymbol logging_symbol[] = {
    host_function("log-u32", log_u32),
};

...

    success = wasm_runtime_register_natives_raw("example:sample/strings", strings_symbol, sizeof(strings_symbol) / sizeof(NativeSymbol));
    success = wasm_runtime_register_natives_raw("example:sample/logging", logging_symbol, sizeof(logging_symbol) / sizeof(NativeSymbol));

view this post on Zulip Gordon Smith (Oct 10 2025 at 21:18):

Getting close to a complete implementation! See https://github.com/GordonSmith/component-model-cpp/tree/WIT-CODEGEN/samples for details, now includes generated stubs from wit definitions...

C++ ABI implementation of the WebAssembly Component Model - GordonSmith/component-model-cpp

view this post on Zulip Gordon Smith (Oct 16 2025 at 15:16):

v0.2.2 successfully generates host stubs + wamr bindings for the entire wit-bindgen test suite.
...and they compile cleanly on windows and nix. (would need host implementations of wasi to actually test them, but they compiled!!!)

view this post on Zulip Gordon Smith (Oct 16 2025 at 15:19):

$ ./wit-codegen.exe --help
wit-codegen - WebAssembly Interface Types (WIT) Code Generator

USAGE:
  C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe <wit-file-or-dir> [output-prefix]
  C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe --help
  C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe -h

ARGUMENTS:
  <wit-file-or-dir> Path to WIT file or directory with WIT package
  [output-prefix]   Optional output file prefix (default: derived from package name)

OPTIONS:
  -h, --help        Show this help message and exit

DESCRIPTION:
  Generates C++ host function bindings from WebAssembly Interface Types (WIT)
  files. The tool parses WIT syntax and generates type-safe C++ code for
  interfacing with WebAssembly components.

  Supports multi-file WIT packages with deps/ folder dependencies.

GENERATED FILES:
  <prefix>.hpp          - C++ header with type definitions and declarations
  <prefix>_wamr.hpp     - WAMR runtime integration header
  <prefix>_wamr.cpp     - WAMR binding implementation with NativeSymbol arrays

FEATURES:
  - Supports all Component Model types (primitives, strings, lists, records,
    variants, enums, options, results, flags)
  - Generates bidirectional bindings (imports and exports)
  - Type-safe C++ wrappers using cmcpp canonical ABI
  - WAMR native function registration helpers
  - Automatic memory management for complex types
  - Multi-file package support with deps/ folder resolution

EXAMPLES:
  # Generate bindings from single WIT file
  C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe example.wit

  # Generate bindings from WIT package directory
  C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe wit/

  # Generate bindings with custom prefix
  C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe example.wit my_bindings

  # Show help message
  C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe --help

For more information, see: https://github.com/GordonSmith/component-model-cpp

view this post on Zulip Gordon Smith (Oct 16 2025 at 15:22):

https://github.com/GordonSmith/component-model-cpp/releases

C++ ABI implementation of the WebAssembly Component Model - GordonSmith/component-model-cpp

Last updated: Dec 06 2025 at 07:03 UTC