Skip to content

feat(cpp): add map type support#1590

Open
yordis wants to merge 16 commits intobytecodealliance:mainfrom
yordis:yordis/feat-cpp-map-support
Open

feat(cpp): add map type support#1590
yordis wants to merge 16 commits intobytecodealliance:mainfrom
yordis:yordis/feat-cpp-map-support

Conversation

@yordis
Copy link
Copy Markdown
Contributor

@yordis yordis commented Apr 14, 2026

This PR adds map type support to the C++ backend, following the same pattern established in #1562 (core), #1583 (Go), and #1584 (MoonBit).

@yordis yordis marked this pull request as draft April 14, 2026 20:31
@alexcrichton
Copy link
Copy Markdown
Member

cc @cpetig and @TartanLlama

@yordis yordis marked this pull request as ready for review April 14, 2026 23:20
Comment thread crates/cpp/src/lib.rs Outdated
Comment thread crates/cpp/src/lib.rs Outdated
Comment thread crates/cpp/src/lib.rs Outdated
Comment thread crates/cpp/src/lib.rs Outdated
Comment thread crates/cpp/src/lib.rs Outdated
Comment thread crates/cpp/src/lib.rs Outdated
Comment thread crates/cpp/src/lib.rs Outdated
@yordis
Copy link
Copy Markdown
Contributor Author

yordis commented Apr 16, 2026

@TartanLlama addressing your comments, the "why" behind things, I am effectively Junior in C++, please be patience with me here 🙏🏻 I am try to understand the intent and I will give it a second path

@cpetig
Copy link
Copy Markdown
Collaborator

cpetig commented Apr 16, 2026

If I were to implement this feature I would introduce a wit::map type which has the same layout as the lowered data. This way the data doesn't need to be copied unless the user code requests it (making map a lower cost abstraction).

On the other hand I just ran into an adjacent problem with list lowering (and avoiding re-allocation) in #1592 , so avoiding a copy in case of complex types might still be impossible - due to C++ type layout restrictions.

A std::map is of course more easy to use if you don't care about allocation and copy costs.

@yordis yordis marked this pull request as draft April 17, 2026 14:58
@yordis yordis requested a review from TartanLlama April 17, 2026 18:10
@yordis yordis marked this pull request as ready for review April 17, 2026 18:10
@yordis
Copy link
Copy Markdown
Contributor Author

yordis commented Apr 17, 2026

@cpetig @TartanLlama can you give it a pass again? 🙏🏻

Ideally, please share the intent of what you would like to see, I can figure out eventually the implementation but I am not qualify to the best practices or insights of C++. I can figure that out for sure, it would help to declare the intent so I can go off, study, and figure out what you want 😄

Comment thread crates/cpp/helper-types/wit.h Outdated
Comment thread crates/cpp/helper-types/wit.h Outdated
@TartanLlama
Copy link
Copy Markdown
Contributor

Hmm I think that providing indexing functions for wit::map is a potential footgun. If the key type is an integral type, users who don't check the docs would likely assume that the argument to operator[] would be a key, rather than an index, but the code would compile fine. I think if we provide a zero-additional-copy wit::map type, it would have to have no indexing functions.

But I think in general there's a big tradeoff in performance vs ergonomics here. Ideally, I think we'd be able to configure whether we wanted a low-copy, low-usability type like wit::map vs a high-usability type that requires extra copies like std::unordered_map. What do you think @cpetig ?

@yordis
Copy link
Copy Markdown
Contributor Author

yordis commented Apr 20, 2026

I am gonna wait until @TartanLlama and @cpetig agree on the direction

Copy link
Copy Markdown
Collaborator

@cpetig cpetig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't feel disheartened by the complexity of designing proper language bindings, it took me more than a year to come up with something I mostly liked - and then @TartanLlama made a significant improvement by pointing out that moved arguments can be received into normal objects.

Sorry, but writing proper bindings is simply complex, and mapping map is more so.

Comment thread crates/cpp/helper-types/wit.h Outdated
Comment thread crates/cpp/helper-types/wit.h Outdated
Comment thread crates/cpp/src/lib.rs Outdated
@cpetig
Copy link
Copy Markdown
Collaborator

cpetig commented Apr 26, 2026

Hmm I think that providing indexing functions for wit::map is a potential footgun. If the key type is an integral type, users who don't check the docs would likely assume that the argument to operator[] would be a key, rather than an index, but the code would compile fine. I think if we provide a zero-additional-copy wit::map type, it would have to have no indexing functions.

I would not want to have functions which aren't part of the standard (unordered_)map - unless they are needed to construct the object within the bindings (or help construct these types in user code).

But I think in general there's a big tradeoff in performance vs ergonomics here. Ideally, I think we'd be able to configure whether we wanted a low-copy, low-usability type like wit::map vs a high-usability type that requires extra copies like std::unordered_map. What do you think @cpetig ?

I don't know which to prefer. This usually indicates that a generator option is a good solution. But I am quite sure that wit::unordered_map would be a better name for this thing. For C/C++/Rust I typically go with avoiding copying in the bindings even if the types get a little more difficult to use. In user code you can always add copying to more convenient types, but you can never remove it once it is done in the bindings.

Also I feel that wit::vector vs std::vector and wit::string vs std::string would be selected by the same option. This feels out of scope for just implementing WebAssembly/component-model#554

yordis added 15 commits April 28, 2026 03:03
…ix rustfmt

wit::string lacked comparison operators, causing compilation failures
when used as a std::map key. Also fixes rustfmt formatting issues.
std::map keys are const, but the ABI lowering needs to call leak() on
string keys to transfer ownership to the flat buffer. Use const_cast
since the map is consumed during the lowering operation.
const_cast fails when the map key type differs between contexts (e.g.
std::string_view in imports vs wit::string in exports). Copying by
value works universally and is safe since the map is consumed.
wasm-component-ld bundled with wasi-sdk 30 doesn't support the map
type encoding (0x63) in the component model binary format. The C++
map codegen is still validated by the codegen test. The runtime test
can be re-added when wasi-sdk ships a compatible wasm-component-ld.
std::map requires per-entry rb-tree allocation during lift; wit::map
mirrors the canonical ABI layout (flat pair array) so lift is a single
allocation, matching the existing wit::vector / wit::string pattern.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
wit::map::size() and std::span::size() already return size_t, so the
C-style cast was a no-op.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Avoids an unused `base` local and the `(void) base;` suppression when
the element has no nested heap-owned fields (e.g. list<u32>, map<char, u32>).

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
The cast converts void* (from cabi_realloc) to uint8_t*, which is a
well-defined static_cast and doesn't need a C-style cast.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Leaves the moved-from map in a fully consistent state so any later
inspection of size()/empty() reflects the empty invariant, not just
the destructor's short-circuit on a null data_ pointer.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Index-based subscript on a map is a footgun: integral keys would
silently compile while doing positional access. Iteration via
range-for and pointer access via data() are sufficient for codegen
and don't carry the same expectation mismatch.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
A span of pairs is a vector-shaped view that doesn't fit a map
abstraction; codegen sites that need a flat pair span build it
explicitly from data() and size() at the call site.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Borrowed map arguments were surfacing as std::span<std::pair<K,V> const>,
which carries vector-shaped affordances (positional indexing, span
conversions) on what is conceptually a map. wit::map_view is a borrow-
only counterpart to wit::map that mimics map semantics rather than
vector semantics.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Component-model map<K,V> carries no ordering or hashing guarantee, and
the type's API surface deliberately mirrors std::unordered_map (size,
empty, begin/end) plus bindings-construction primitives, so the name
should reflect the unordered semantics rather than std::map's ordered
ones. Companion borrow type renamed to wit::unordered_map_view.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis force-pushed the yordis/feat-cpp-map-support branch from 6387484 to 8e357c2 Compare April 28, 2026 07:06
Adjacent block bodies (string/list/option dealloc) now reference _base
after main's rename, so MapLower / MapLift / GuestDeallocateMap must
declare the per-entry pointer under the same name to compile. Adds the
matching (void) _base; suppression so loops still compile when the
inner body doesn't reference it.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis requested review from TartanLlama and cpetig April 28, 2026 08:22
@yordis
Copy link
Copy Markdown
Contributor Author

yordis commented Apr 28, 2026

@TartanLlama @cpetig give it another pass please 🙏🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants