Skip to content

Commit 96e0152

Browse files
authored
C++17 bindings (uses "modern" data types for option,result,resource) (#1283)
* initial c++ skeleton * reduce code (no host, no symmetric) * c++ generator compiles * skeletal c++ testing support * proper include * post merge fixes * c++ numbers test works * many-arguments c++ test works * more test changes * records test passes * records test passes for c++ * numbers ok with c++17 * many arguments cover both sides * strings test passes * fix options test * fix old string test * list test (in progress) * fix list test * incomplete results test * better variant support * small fix * use c++17 to compile * remove unnecessary move * variant lowering * correct record lifting * fix variant lifting * results test passing (but incomplete) * results test fully operational * Opt out of language C++17 for cpp extension * variants work * variants runner works * clean up lists test * I need a better strategy to copy the headers to the target * not elegant but working solution * working resources * more complex resource test * remove unused functions * generate template files in the output directory * simplifications and clippy fixes * this was never meant to be checked in * cargo fmt again * fix publishing logic
1 parent 3ebafeb commit 96e0152

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+7541
-5
lines changed

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ wasm-compose = "0.234.0"
4141

4242
wit-bindgen-core = { path = 'crates/core', version = '0.42.1' }
4343
wit-bindgen-c = { path = 'crates/c', version = '0.42.1' }
44+
wit-bindgen-cpp = { path = 'crates/cpp', version = '0.42.1' }
4445
wit-bindgen-rust = { path = "crates/rust", version = "0.42.1" }
4546
wit-bindgen-csharp = { path = 'crates/csharp', version = '0.42.1' }
4647
wit-bindgen-markdown = { path = 'crates/markdown', version = '0.42.1' }
@@ -57,6 +58,7 @@ clap = { workspace = true, features = ['wrap_help'] }
5758
wit-bindgen-core = { workspace = true }
5859
wit-bindgen-rust = { workspace = true, features = ['clap'], optional = true }
5960
wit-bindgen-c = { workspace = true, features = ['clap'], optional = true }
61+
wit-bindgen-cpp = { workspace = true, features = ['clap'], optional = true }
6062
wit-bindgen-markdown = { workspace = true, features = ['clap'], optional = true }
6163
wit-bindgen-moonbit = { workspace = true, features = ['clap'], optional = true }
6264
wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true }
@@ -72,10 +74,12 @@ default = [
7274
'markdown',
7375
'go',
7476
'csharp',
77+
'cpp',
7578
'moonbit',
7679
'async',
7780
]
7881
c = ['dep:wit-bindgen-c']
82+
cpp = ['dep:wit-bindgen-cpp']
7983
rust = ['dep:wit-bindgen-rust']
8084
markdown = ['dep:wit-bindgen-markdown']
8185
go = []

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,14 @@ Then, you can generate the bindings for your project:
385385
wit-bindgen-go generate <path-to-wit-pkg>
386386
```
387387

388+
### Guest: C++-17+
389+
390+
The cpp crate contains code to generate C++ code which uses the std types
391+
optional, string, string_view, vector, expected to represent generic
392+
WIT types.
393+
394+
This relies on wasi-SDK for guest compilation.
395+
388396
### Guest: MoonBit
389397

390398
MoonBit can be compiled to WebAssembly using [its toolchain](https://moonbitlang.com/download):

ci/publish.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use std::time::Duration;
1919
const CRATES_TO_PUBLISH: &[&str] = &[
2020
"wit-bindgen-core",
2121
"wit-bindgen-c",
22+
"wit-bindgen-cpp",
2223
"wit-bindgen-rust",
2324
"wit-bindgen-csharp",
2425
"wit-bindgen-markdown",

crates/c/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
mod component_type_object;
1+
pub mod component_type_object;
22

33
use anyhow::Result;
44
use heck::*;
@@ -3865,7 +3865,7 @@ impl Source {
38653865
}
38663866
}
38673867

3868-
fn wasm_type(ty: WasmType) -> &'static str {
3868+
pub fn wasm_type(ty: WasmType) -> &'static str {
38693869
match ty {
38703870
WasmType::I32 => "int32_t",
38713871
WasmType::I64 => "int64_t",

crates/cpp/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "wit-bindgen-cpp"
3+
authors = ["Christof Petig <christof.petig@arcor.de>"]
4+
version = "0.42.1"
5+
edition.workspace = true
6+
repository = 'https://github.com/cpetig/wit-bindgen'
7+
license = "Apache-2.0 WITH LLVM-exception"
8+
description = """
9+
C++ guest and host binding generator for WIT and the component model.
10+
"""
11+
12+
[lib]
13+
doctest = false
14+
test = false
15+
16+
[dependencies]
17+
wit-bindgen-core = { workspace = true }
18+
wit-component = { workspace = true }
19+
wasm-encoder = { workspace = true }
20+
wasm-metadata = { workspace = true }
21+
wit-bindgen-c = { workspace = true }
22+
anyhow = { workspace = true }
23+
heck = { workspace = true }
24+
clap = { workspace = true, optional = true }
25+
26+
[dev-dependencies]
27+
test-helpers = { path = '../test-helpers' }

crates/cpp/DESIGN.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Type mapping
2+
3+
| Code | Environment |
4+
| --- | --- |
5+
| G-- | guest side |
6+
| H-- | host side |
7+
| -I- | guest-import (guest calls) |
8+
| -E- | guest-export (host calls) |
9+
| --A | argument |
10+
| --R | result |
11+
| --S | in struct |
12+
13+
| mode | |
14+
| --- | --- |
15+
| v | passed by value |
16+
| t | owernership transferred |
17+
| p | cabi_post_ cleans up |
18+
19+
| API | | | ABI | |
20+
| --- | --- | --- | --- | --- |
21+
| 🕸 | old | | 📘 | canonical |
22+
| 💎 | new | | 🪞 | symmetric |
23+
24+
| Code | mode | WIT Type | Rust type | C++ Type | Lower | Reason |
25+
| --- | --- | --- | --- | --- | --- | --- |
26+
| GIA | v | string | &str[^1] | string_view (17) | addr, len | |
27+
| | | list | &[T] | wit::span [^5] | addr, len | |
28+
| | | tuple | (...) | std::tuple | 0, 1, ...| |
29+
| | | tuple<string, list> | (&str, &[T]) | std::tuple<...> | a,l,a,l |
30+
| | | record{string, list} | &T | T const& | a,l,a,l |
31+
| | | large-struct (>16 args) | &T | T const& | &t |
32+
| | | result<string,list> | Result<&str, &[]> | std::expected<string_view, span> | d,a,l |
33+
| | | option\<string> | Option\<&str> | optional<string_view> const& | d,a,l|
34+
| | | list\<resrc> | &[\&Resrc]? | vector<string_view> const& | a,l|
35+
| GIR | t | string | String | wit::string[^2] | &(addr, len) [^8] | |
36+
| | | list | Vec | wit::vector | &(a,l) |
37+
| | | result<string,list> | Result<String, Vec> | std::expected<wit::string, wit::vector> | &(d,a,l) |
38+
| GEA | t | string | String | 🕸 wit::string&& | addr, len |
39+
| | | | | 💎 string_view | |
40+
| | | result<string,list> | Result<String, Vec> | 🕸 std::expected<wit::string, wit::vector>&& | d,a,l |
41+
| | | | | 💎 std::expected<string_view, wit::span> | |
42+
| GER | p | string | String | wit::string (or std?) | 📘 -> &(a,l) cabi_post_N:P/I#F [^7] |
43+
| | | | | | 🪞 &(a,l) |
44+
| | | result<string,list> | Result<String, Vec> | std::expected<wit::string, wit::vector> | 📘 -> &(d,a,l) cabi_post |
45+
| --S | ? | string | String | wit::string | addr, len |
46+
| HIA | v | string | | string_view | a,l |
47+
| HIR | t | string | | wit::string[^3] | &(a,l) |
48+
| HEA | t | string | | 🕸 wit::string[^4] | a,l |
49+
| | | | | 💎 string_view [^6] | |
50+
| HER | p | string | | 🕸 wit::guest_owned<string_view> | 📘 -> &(a,l) |
51+
| | | | | 💎 wit::string [^6] | 🪞 &(a,l) |
52+
53+
[^1]: The host never frees memory (is never passed ownership)!
54+
55+
[^2]: A wit::string is identical to the canonical representation, so it can be part of structures. On the guest a wit::string owns the memory and frees it after use.
56+
On the host a wit::string can be constructed(=allocated) with an exec_env argument. Thus, without an exec_env a wit::string on the host is inaccessible.
57+
Complex (non-POD) struct elements on the host will need exec_env to decode or construct.
58+
59+
[^3]: A wit::string requires exec_env inside the host implementation. ~~Perhaps a flexible type (either std::string or wit::string would be possible), or make this a generation option?~~ std::string requires a copy, wit::string requires passing exec_env to the method (which is necessary for methods anyway).
60+
61+
[^4]: A host side wit::string doesn't own the data (not free in dtor), thus no move semantics.
62+
63+
[^5]: std::span requires C++-20, this alias should give minimal functionality with older compiler targets.
64+
65+
[^6]: Not implemented, for now symmetric is priority
66+
67+
[^7]: Here the callee (guest) allocates the memory for the set on its side
68+
69+
[^8]: Caller passes address of the return object as argument
70+
71+
## [Symmetric ABI](https://github.com/WebAssembly/component-model/issues/386)
72+
73+
The idea is to directly connect (link) components to each other.
74+
75+
Thus imported and exported functions and resources need to be compatible
76+
at the ABI level.
77+
78+
For now for functions the guest import convention is used in both directions:
79+
80+
- The imported function ABI is used with the following properties
81+
82+
- (unchanged) List and string arguments are passed as Views, no free
83+
required, lifetime is constrained until the end of the call
84+
85+
- (unchanged) Owned resources in arguments or results pass ownership
86+
to the callee
87+
88+
- (unchanged) If there are too many (>1) flat results, a local
89+
uninitialized ret_area is passed via the last argument
90+
91+
- (unchanged) Returned objects are owned.
92+
For functional safety, i.e. avoiding all
93+
allocations in the hot path, the hope is with [#385](https://github.com/WebAssembly/component-model/issues/385).
94+
95+
- The imported resource ABI is used also for exporting
96+
with one modification:
97+
98+
Resource IDs become usize, so you can optimize the resource table away.

crates/cpp/helper-types/wit-common.h

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#pragma once
2+
3+
#include <assert.h>
4+
#include <map>
5+
#include <optional>
6+
#include <stddef.h> // size_t
7+
#include <stdint.h>
8+
#if __cplusplus > 202001L
9+
#include <span>
10+
#else
11+
#include <vector>
12+
#endif
13+
14+
namespace wit {
15+
#if __cplusplus > 202001L
16+
using std::span;
17+
#else
18+
/// Minimal span (vector view) implementation for older C++ environments
19+
template <class T> class span {
20+
T const *address;
21+
size_t length;
22+
23+
public:
24+
T const *data() const { return address; }
25+
size_t size() const { return length; }
26+
27+
typedef T const *const_iterator;
28+
29+
const_iterator begin() const { return address; }
30+
const_iterator end() const { return address + length; }
31+
bool empty() const { return !length; }
32+
T const &operator[](size_t index) const { return address[index]; }
33+
span(T *a, size_t l) : address(a), length(l) {}
34+
// create from any compatible vector (borrows data!)
35+
template <class U>
36+
span(std::vector<U> const &vec) : address(vec.data()), length(vec.size()) {}
37+
};
38+
#endif
39+
40+
/// @brief Helper class to map between IDs and resources
41+
/// @tparam R Type of the Resource
42+
template <class R> class ResourceTable {
43+
static std::map<int32_t, R> resources;
44+
45+
public:
46+
static R *lookup_resource(int32_t id) {
47+
auto result = resources.find(id);
48+
return result == resources.end() ? nullptr : &result->second;
49+
}
50+
static int32_t store_resource(R &&value) {
51+
auto last = resources.rbegin();
52+
int32_t id = last == resources.rend() ? 0 : last->first + 1;
53+
resources.insert(std::pair<int32_t, R>(id, std::move(value)));
54+
return id;
55+
}
56+
static std::optional<R> remove_resource(int32_t id) {
57+
auto iter = resources.find(id);
58+
std::optional<R> result;
59+
if (iter != resources.end()) {
60+
result = std::move(iter->second);
61+
resources.erase(iter);
62+
}
63+
return std::move(result);
64+
}
65+
};
66+
67+
/// @brief Replaces void in the error position of a result
68+
struct Void {};
69+
70+
template<class To, class From>
71+
constexpr To bit_cast(const From& from) noexcept {
72+
union Bits { From from; To to; };
73+
Bits b; b.from = from; return b.to;
74+
}
75+
} // namespace wit

0 commit comments

Comments
 (0)