Skip to content

Commit af57bb6

Browse files
committed
feat: add async runtime
helpers fixes #4 Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
1 parent d1df1af commit af57bb6

File tree

4 files changed

+491
-0
lines changed

4 files changed

+491
-0
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ This repository contains a C++ ABI implementation of the WebAssembly Component M
5959
- [x] Wamr
6060
- [ ] WasmEdge
6161

62+
When expanding the canonical ABI surface, cross-check the Python reference tests in `ref/component-model/design/mvp/canonical-abi/run_tests.py`; new host features should mirror the behaviors exercised there.
63+
6264
## Build Instructions
6365

6466
### Prerequisites
@@ -171,6 +173,46 @@ This library is a header only library. To use it in your project, you can:
171173
- [x] Copy the contents of the `include` directory to your project.
172174
- [ ] Use `vcpkg` to install the library and its dependencies.
173175

176+
### Async runtime helpers
177+
178+
The canonical Component Model runtime is cooperative: hosts must drive pending work by scheduling tasks explicitly. `cmcpp` now provides a minimal async harness in `cmcpp/runtime.hpp`:
179+
180+
- `Store` owns the pending task queue and exposes `invoke` plus `tick()`.
181+
- `FuncInst` is the callable signature hosts use to wrap guest functions.
182+
- `Thread::create` builds a pending task with user-supplied readiness/resume callbacks.
183+
- `Call::from_thread` returns a cancellation-capable handle to the caller.
184+
185+
Typical usage:
186+
187+
```cpp
188+
cmcpp::Store store;
189+
cmcpp::FuncInst func = [](cmcpp::Store &store,
190+
cmcpp::SupertaskPtr,
191+
cmcpp::OnStart on_start,
192+
cmcpp::OnResolve on_resolve) {
193+
auto args = std::make_shared<std::vector<std::any>>(on_start());
194+
auto gate = std::make_shared<std::atomic<bool>>(false);
195+
196+
auto thread = cmcpp::Thread::create(
197+
store,
198+
[gate]() { return gate->load(); },
199+
[args, on_resolve](bool cancelled) {
200+
on_resolve(cancelled ? std::nullopt : std::optional{*args});
201+
return false; // finished
202+
},
203+
true,
204+
[gate]() { gate->store(true); });
205+
206+
return cmcpp::Call::from_thread(thread);
207+
};
208+
209+
auto call = store.invoke(func, nullptr, [] { return std::vector<std::any>{}; }, [](auto) {});
210+
// Drive progress
211+
store.tick();
212+
```
213+
214+
Call `tick()` in your host loop until all pending work completes. Cancellation is cooperative: calling `Call::request_cancellation()` marks the associated thread as cancelled before the next `tick()`.
215+
174216
175217
## Related projects
176218

include/cmcpp.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
#include <cmcpp/func.hpp>
1212
#include <cmcpp/lower.hpp>
1313
#include <cmcpp/lift.hpp>
14+
#include <cmcpp/runtime.hpp>
1415

1516
#endif // CMCPP_HPP

include/cmcpp/runtime.hpp

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
#ifndef CMCPP_RUNTIME_HPP
2+
#define CMCPP_RUNTIME_HPP
3+
4+
#include <algorithm>
5+
#include <any>
6+
#include <functional>
7+
#include <memory>
8+
#include <mutex>
9+
#include <optional>
10+
#include <utility>
11+
#include <vector>
12+
13+
namespace cmcpp
14+
{
15+
class Store;
16+
17+
struct Supertask;
18+
using SupertaskPtr = std::shared_ptr<Supertask>;
19+
20+
using OnStart = std::function<std::vector<std::any>()>;
21+
using OnResolve = std::function<void(std::optional<std::vector<std::any>>)>;
22+
23+
class Thread : public std::enable_shared_from_this<Thread>
24+
{
25+
public:
26+
using ReadyFn = std::function<bool()>;
27+
using ResumeFn = std::function<bool(bool)>;
28+
using CancelFn = std::function<void()>;
29+
30+
static std::shared_ptr<Thread> create(Store &store, ReadyFn ready, ResumeFn resume, bool cancellable = false, CancelFn on_cancel = {});
31+
32+
Thread(Store &store, ReadyFn ready, ResumeFn resume, bool cancellable, CancelFn on_cancel);
33+
bool ready() const;
34+
void resume();
35+
void request_cancellation();
36+
bool cancellable() const;
37+
bool cancelled() const;
38+
bool completed() const;
39+
40+
private:
41+
enum class State
42+
{
43+
Pending,
44+
Running,
45+
Completed
46+
};
47+
48+
void set_pending(bool pending_again, const std::shared_ptr<Thread> &self);
49+
50+
Store *store_;
51+
ReadyFn ready_;
52+
ResumeFn resume_;
53+
CancelFn on_cancel_;
54+
bool cancellable_;
55+
bool cancelled_;
56+
mutable std::mutex mutex_;
57+
State state_;
58+
};
59+
60+
class Call
61+
{
62+
public:
63+
using CancelRequest = std::function<void()>;
64+
65+
Call() = default;
66+
explicit Call(CancelRequest cancel) : request_cancellation_(std::move(cancel)) {}
67+
68+
void request_cancellation() const
69+
{
70+
if (request_cancellation_)
71+
{
72+
request_cancellation_();
73+
}
74+
}
75+
76+
static Call from_thread(const std::shared_ptr<Thread> &thread)
77+
{
78+
if (!thread)
79+
{
80+
return Call();
81+
}
82+
std::weak_ptr<Thread> weak = thread;
83+
return Call([weak]()
84+
{
85+
if (auto locked = weak.lock())
86+
{
87+
locked->request_cancellation();
88+
} });
89+
}
90+
91+
private:
92+
CancelRequest request_cancellation_;
93+
};
94+
95+
struct Supertask
96+
{
97+
SupertaskPtr parent;
98+
};
99+
100+
using FuncInst = std::function<Call(Store &, SupertaskPtr, OnStart, OnResolve)>;
101+
102+
class Store
103+
{
104+
public:
105+
Call invoke(const FuncInst &func, SupertaskPtr caller, OnStart on_start, OnResolve on_resolve);
106+
void tick();
107+
void schedule(const std::shared_ptr<Thread> &thread);
108+
std::size_t pending_size() const;
109+
110+
private:
111+
friend class Thread;
112+
mutable std::mutex mutex_;
113+
std::vector<std::shared_ptr<Thread>> pending_;
114+
};
115+
116+
inline std::shared_ptr<Thread> Thread::create(Store &store, ReadyFn ready, ResumeFn resume, bool cancellable, CancelFn on_cancel)
117+
{
118+
auto thread = std::shared_ptr<Thread>(new Thread(store, std::move(ready), std::move(resume), cancellable, std::move(on_cancel)));
119+
store.schedule(thread);
120+
return thread;
121+
}
122+
123+
inline Thread::Thread(Store &store, ReadyFn ready, ResumeFn resume, bool cancellable, CancelFn on_cancel)
124+
: store_(&store), ready_(std::move(ready)), resume_(std::move(resume)), on_cancel_(std::move(on_cancel)), cancellable_(cancellable), cancelled_(false), state_(State::Pending)
125+
{
126+
}
127+
128+
inline bool Thread::ready() const
129+
{
130+
std::lock_guard lock(mutex_);
131+
if (state_ != State::Pending)
132+
{
133+
return false;
134+
}
135+
if (!ready_)
136+
{
137+
return true;
138+
}
139+
return ready_();
140+
}
141+
142+
inline void Thread::resume()
143+
{
144+
auto self = shared_from_this();
145+
ResumeFn resume;
146+
bool was_cancelled = false;
147+
{
148+
std::lock_guard lock(mutex_);
149+
if (state_ != State::Pending)
150+
{
151+
return;
152+
}
153+
state_ = State::Running;
154+
resume = resume_;
155+
was_cancelled = cancelled_;
156+
}
157+
158+
bool keep_pending = false;
159+
if (resume)
160+
{
161+
keep_pending = resume(was_cancelled);
162+
}
163+
164+
set_pending(keep_pending, self);
165+
}
166+
167+
inline void Thread::request_cancellation()
168+
{
169+
CancelFn cancel;
170+
{
171+
std::lock_guard lock(mutex_);
172+
if (cancelled_ || !cancellable_)
173+
{
174+
return;
175+
}
176+
cancelled_ = true;
177+
cancel = on_cancel_;
178+
}
179+
if (cancel)
180+
{
181+
cancel();
182+
}
183+
}
184+
185+
inline bool Thread::cancellable() const
186+
{
187+
std::lock_guard lock(mutex_);
188+
return cancellable_;
189+
}
190+
191+
inline bool Thread::cancelled() const
192+
{
193+
std::lock_guard lock(mutex_);
194+
return cancelled_;
195+
}
196+
197+
inline bool Thread::completed() const
198+
{
199+
std::lock_guard lock(mutex_);
200+
return state_ == State::Completed;
201+
}
202+
203+
inline void Thread::set_pending(bool pending_again, const std::shared_ptr<Thread> &self)
204+
{
205+
{
206+
std::lock_guard lock(mutex_);
207+
state_ = pending_again ? State::Pending : State::Completed;
208+
}
209+
if (pending_again)
210+
{
211+
store_->schedule(self);
212+
}
213+
}
214+
215+
inline Call Store::invoke(const FuncInst &func, SupertaskPtr caller, OnStart on_start, OnResolve on_resolve)
216+
{
217+
if (!func)
218+
{
219+
return Call();
220+
}
221+
return func(*this, std::move(caller), std::move(on_start), std::move(on_resolve));
222+
}
223+
224+
inline void Store::tick()
225+
{
226+
std::shared_ptr<Thread> selected;
227+
{
228+
std::lock_guard lock(mutex_);
229+
auto it = std::find_if(pending_.begin(), pending_.end(), [](const std::shared_ptr<Thread> &thread)
230+
{ return thread && thread->ready(); });
231+
if (it == pending_.end())
232+
{
233+
return;
234+
}
235+
selected = *it;
236+
pending_.erase(it);
237+
}
238+
if (selected)
239+
{
240+
selected->resume();
241+
}
242+
}
243+
244+
inline void Store::schedule(const std::shared_ptr<Thread> &thread)
245+
{
246+
if (!thread)
247+
{
248+
return;
249+
}
250+
std::lock_guard lock(mutex_);
251+
pending_.push_back(thread);
252+
}
253+
254+
inline std::size_t Store::pending_size() const
255+
{
256+
std::lock_guard lock(mutex_);
257+
return pending_.size();
258+
}
259+
}
260+
261+
#endif

0 commit comments

Comments
 (0)