Skip to content

Commit 56d49b4

Browse files
authored
feat(c): Add co-op threading builtins to C generator (#1491)
* Add threading builtins to C generator * Add generate-async-helpers flag * Update tests * Add ldflags option * Change context_get and context_set * Split out to new flag * Use globals for test * Format * Compile test with optimizations
1 parent a670e8c commit 56d49b4

File tree

12 files changed

+242
-33
lines changed

12 files changed

+242
-33
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ jobs:
161161
cargo run test --languages rust,c,go tests/runtime-async \
162162
--artifacts target/artifacts \
163163
--rust-wit-bindgen-path ./crates/guest-rust \
164-
--runner "wasmtime -W component-model-async"
164+
--runner "wasmtime -W component-model-async -W component-model-threading -W component-model-async-stackful"
165165
166166
test_unit:
167167
name: Crate Unit Tests

crates/c/src/lib.rs

Lines changed: 137 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pub struct Opts {
103103
#[cfg_attr(feature = "clap", arg(long, value_name = "NAME"))]
104104
pub rename_world: Option<String>,
105105

106-
/// Add the specified suffix to the name of the custome section containing
106+
/// Add the specified suffix to the name of the custom section containing
107107
/// the component type.
108108
#[cfg_attr(feature = "clap", arg(long, value_name = "STRING"))]
109109
pub type_section_suffix: Option<String>,
@@ -121,6 +121,14 @@ pub struct Opts {
121121

122122
#[cfg_attr(feature = "clap", clap(flatten))]
123123
pub async_: AsyncFilterSet,
124+
125+
/// Force generation of async helpers even if no async functions/futures are present.
126+
#[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
127+
pub generate_async_helpers: bool,
128+
129+
/// Generate helpers for threading builtins. Implies `--generate-async-helpers`.
130+
#[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
131+
pub generate_threading_helpers: bool,
124132
}
125133

126134
#[cfg(feature = "clap")]
@@ -432,9 +440,16 @@ impl WorldGenerator for C {
432440
"\nunion double_int64 {{ double a; int64_t b; }};"
433441
);
434442
}
435-
if self.needs_async || self.futures.len() > 0 {
443+
if self.needs_async
444+
|| self.futures.len() > 0
445+
|| self.opts.generate_async_helpers
446+
|| self.opts.generate_threading_helpers
447+
{
436448
self.generate_async_helpers();
437449
}
450+
if self.opts.generate_threading_helpers {
451+
self.generate_threading_helpers();
452+
}
438453
let version = env!("CARGO_PKG_VERSION");
439454
let mut h_str = wit_bindgen_core::Source::default();
440455

@@ -703,6 +718,115 @@ impl C {
703718
}
704719
}
705720

721+
fn generate_threading_helpers(&mut self) {
722+
let snake = self.world.to_snake_case();
723+
uwriteln!(
724+
self.src.h_async,
725+
"
726+
void* {snake}_context_get_1(void);
727+
void {snake}_context_set_1(void* value);
728+
uint32_t {snake}_thread_yield_cancellable(void);
729+
uint32_t {snake}_thread_index(void);
730+
uint32_t {snake}_thread_new_indirect(void (*start_function)(void*), void* arg);
731+
void {snake}_thread_switch_to(uint32_t thread);
732+
uint32_t {snake}_thread_switch_to_cancellable(uint32_t thread);
733+
void {snake}_thread_resume_later(uint32_t thread);
734+
void {snake}_thread_yield_to(uint32_t thread);
735+
uint32_t {snake}_thread_yield_to_cancellable(uint32_t thread);
736+
void {snake}_thread_suspend(void);
737+
uint32_t {snake}_thread_suspend_cancellable(void);
738+
"
739+
);
740+
uwriteln!(
741+
self.src.c_async,
742+
r#"
743+
__attribute__((__import_module__("$root"), __import_name__("[context-get-1]")))
744+
extern void* __context_get_1(void);
745+
746+
void* {snake}_context_get_1(void) {{
747+
return __context_get_1();
748+
}}
749+
750+
__attribute__((__import_module__("$root"), __import_name__("[context-set-1]")))
751+
extern void __context_set_1(void*);
752+
753+
void {snake}_context_set_1(void* value) {{
754+
__context_set_1(value);
755+
}}
756+
757+
__attribute__((__import_module__("$root"), __import_name__("[cancellable][thread-yield]")))
758+
extern uint32_t __thread_yield_cancellable(void);
759+
760+
uint32_t {snake}_thread_yield_cancellable(void) {{
761+
return __thread_yield_cancellable();
762+
}}
763+
764+
__attribute__((__import_module__("$root"), __import_name__("[thread-index]")))
765+
extern uint32_t __thread_index(void);
766+
767+
uint32_t {snake}_thread_index(void) {{
768+
return __thread_index();
769+
}}
770+
771+
__attribute__((__import_module__("$root"), __import_name__("[thread-new-indirect-v0]")))
772+
extern uint32_t __thread_new_indirect(uint32_t, void*);
773+
774+
uint32_t {snake}_thread_new_indirect(void (*start_function)(void*), void* arg) {{
775+
return __thread_new_indirect((uint32_t)(uintptr_t)start_function, arg
776+
);
777+
}}
778+
779+
__attribute__((__import_module__("$root"), __import_name__("[thread-switch-to]")))
780+
extern uint32_t __thread_switch_to(uint32_t);
781+
782+
void {snake}_thread_switch_to(uint32_t thread) {{
783+
__thread_switch_to(thread);
784+
}}
785+
786+
__attribute__((__import_module__("$root"), __import_name__("[cancellable][thread-switch-to]")))
787+
extern uint32_t __thread_switch_to_cancellable(uint32_t);
788+
789+
uint32_t {snake}_thread_switch_to_cancellable(uint32_t thread) {{
790+
return __thread_switch_to_cancellable(thread);
791+
}}
792+
793+
__attribute__((__import_module__("$root"), __import_name__("[thread-resume-later]")))
794+
extern void __thread_resume_later(uint32_t);
795+
796+
void {snake}_thread_resume_later(uint32_t thread) {{
797+
__thread_resume_later(thread);
798+
}}
799+
800+
__attribute__((__import_module__("$root"), __import_name__("[thread-yield-to]")))
801+
extern uint32_t __thread_yield_to(uint32_t);
802+
803+
void {snake}_thread_yield_to(uint32_t thread) {{
804+
__thread_yield_to(thread);
805+
}}
806+
807+
__attribute__((__import_module__("$root"), __import_name__("[cancellable][thread-yield-to]")))
808+
extern uint32_t __thread_yield_to_cancellable(uint32_t);
809+
810+
uint32_t {snake}_thread_yield_to_cancellable(uint32_t thread) {{
811+
return __thread_yield_to_cancellable(thread);
812+
}}
813+
814+
__attribute__((__import_module__("$root"), __import_name__("[thread-suspend]")))
815+
extern uint32_t __thread_suspend(void);
816+
817+
void {snake}_thread_suspend(void) {{
818+
__thread_suspend();
819+
}}
820+
821+
__attribute__((__import_module__("$root"), __import_name__("[cancellable][thread-suspend]")))
822+
extern uint32_t __thread_suspend_cancellable(void);
823+
uint32_t {snake}_thread_suspend_cancellable(void) {{
824+
return __thread_suspend_cancellable();
825+
}}
826+
"#
827+
);
828+
}
829+
706830
fn generate_async_helpers(&mut self) {
707831
let snake = self.world.to_snake_case();
708832
let shouty = self.world.to_shouty_snake_case();
@@ -768,10 +892,9 @@ typedef enum {snake}_waitable_state {{
768892
769893
void {snake}_backpressure_inc(void);
770894
void {snake}_backpressure_dec(void);
771-
void* {snake}_context_get(void);
772-
void {snake}_context_set(void*);
773-
void {snake}_yield(void);
774-
uint32_t {snake}_yield_cancellable(void);
895+
void* {snake}_context_get_0(void);
896+
void {snake}_context_set_0(void* value);
897+
void {snake}_thread_yield(void);
775898
"
776899
);
777900
uwriteln!(
@@ -847,32 +970,26 @@ void {snake}_backpressure_dec(void) {{
847970
}}
848971
849972
__attribute__((__import_module__("$root"), __import_name__("[context-get-0]")))
850-
extern void* __context_get(void);
973+
extern void* __context_get_0(void);
851974
852-
void* {snake}_context_get() {{
853-
return __context_get();
975+
void* {snake}_context_get_0(void) {{
976+
return __context_get_0();
854977
}}
855978
856979
__attribute__((__import_module__("$root"), __import_name__("[context-set-0]")))
857-
extern void __context_set(void*);
980+
extern void __context_set_0(void*);
858981
859-
void {snake}_context_set(void *val) {{
860-
return __context_set(val);
982+
983+
void {snake}_context_set_0(void *value) {{
984+
__context_set_0(value);
861985
}}
862986
863987
__attribute__((__import_module__("$root"), __import_name__("[thread-yield]")))
864988
extern uint32_t __thread_yield(void);
865989
866-
void {snake}_yield(void) {{
990+
void {snake}_thread_yield(void) {{
867991
__thread_yield();
868992
}}
869-
870-
__attribute__((__import_module__("$root"), __import_name__("[cancellable][thread-yield]")))
871-
extern uint32_t __thread_yield_cancellable(void);
872-
873-
uint32_t {snake}_yield_cancellable(void) {{
874-
return __thread_yield_cancellable();
875-
}}
876993
"#
877994
);
878995
}

crates/test/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,10 @@ separate crate and passed as `--extern`
314314

315315
#### Configuration: C
316316

317-
C/C++ configuration supports configuring compilation flags at this time:
317+
C/C++ configuration supports configuring compilation and linker flags at this time:
318318

319319
```rust
320320
//@ [lang]
321321
//@ cflags = '-O'
322+
//@ ldflags = "-Wl,--export-table"
322323
```

crates/test/src/c.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ struct LangConfig {
2828
/// Space-separated list or array of compiler flags to pass.
2929
#[serde(default)]
3030
cflags: StringList,
31+
/// Space-separated list or array of linker flags to pass.
32+
#[serde(default)]
33+
ldflags: StringList,
3134
}
3235

3336
fn clang(runner: &Runner<'_>) -> PathBuf {
@@ -144,6 +147,9 @@ fn compile(runner: &Runner<'_>, compile: &Compile<'_>, compiler: PathBuf) -> Res
144147
for flag in Vec::from(config.cflags) {
145148
cmd.arg(flag);
146149
}
150+
for flag in Vec::from(config.ldflags) {
151+
cmd.arg(flag);
152+
}
147153
cmd.arg("-mexec-model=reactor");
148154
if produces_component(runner) {
149155
cmd.arg("-Wl,--skip-wit-component");

tests/runtime-async/async/cancel-import/test.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ test_callback_code_t exports_test_pending_import(exports_test_future_void_t x) {
1818
task->set = test_waitable_set_new();
1919
test_waitable_join(task->future, task->set);
2020

21-
test_context_set(task);
21+
test_context_set_0(task);
2222
return TEST_CALLBACK_CODE_WAIT(task->set);
2323
}
2424

2525
test_callback_code_t exports_test_pending_import_callback(test_event_t *event) {
26-
struct my_task *task = (struct my_task*) test_context_get();
26+
struct my_task *task = (struct my_task*) test_context_get_0();
2727
if (event->event == TEST_EVENT_CANCEL) {
2828
assert(event->waitable == 0);
2929
assert(event->code == 0);

tests/runtime-async/async/future-cancel-read/test.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ test_callback_code_t exports_test_start_read_then_cancel(
5858

5959
test_waitable_join(signal, state->set);
6060

61-
test_context_set(state);
61+
test_context_set_0(state);
6262
return TEST_CALLBACK_CODE_WAIT(state->set);
6363
}
6464

6565
test_callback_code_t exports_test_start_read_then_cancel_callback(test_event_t *event) {
6666
struct start_read_then_cancel_state *state =
67-
(struct start_read_then_cancel_state*) test_context_get();
67+
(struct start_read_then_cancel_state*) test_context_get_0();
6868
assert(event->event == TEST_EVENT_FUTURE_READ);
6969
assert(event->waitable == state->signal);
7070
assert(TEST_WAITABLE_STATE(event->code) == TEST_WAITABLE_COMPLETED);

tests/runtime-async/async/pending-import/test.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ test_callback_code_t exports_test_pending_import(exports_test_future_void_t x) {
1818
task->set = test_waitable_set_new();
1919
test_waitable_join(task->future, task->set);
2020

21-
test_context_set(task);
21+
test_context_set_0(task);
2222
return TEST_CALLBACK_CODE_WAIT(task->set);
2323
}
2424

2525
test_callback_code_t exports_test_pending_import_callback(test_event_t *event) {
26-
struct my_task *task = (struct my_task*) test_context_get();
26+
struct my_task *task = (struct my_task*) test_context_get_0();
2727
assert(event->event == TEST_EVENT_FUTURE_READ);
2828
assert(event->waitable == task->future);
2929
assert(TEST_WAITABLE_STATE(event->code) == TEST_WAITABLE_COMPLETED);

tests/runtime-async/async/ping-pong/test.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ test_callback_code_t exports_test_ping(exports_test_future_string_t x, test_stri
3333

3434
// Register ourselves as waiting on the future, then block our task.
3535
test_waitable_join(task->future, task->set);
36-
test_context_set(task);
36+
test_context_set_0(task);
3737
return TEST_CALLBACK_CODE_WAIT(task->set);
3838
}
3939

4040
test_callback_code_t exports_test_ping_callback(test_event_t *event) {
41-
struct ping_task *task = (struct ping_task*) test_context_get();
41+
struct ping_task *task = (struct ping_task*) test_context_get_0();
4242
switch (task->state) {
4343
case PING_S1: {
4444
// Assert that our future read completed and discard the read end of the
@@ -126,12 +126,12 @@ test_callback_code_t exports_test_pong(exports_test_future_string_t x) {
126126
assert(status == TEST_WAITABLE_STATUS_BLOCKED);
127127
test_waitable_join(task->future, task->set);
128128

129-
test_context_set(task);
129+
test_context_set_0(task);
130130
return TEST_CALLBACK_CODE_WAIT(task->set);
131131
}
132132

133133
test_callback_code_t exports_test_pong_callback(test_event_t *event) {
134-
struct pong_task *task = (struct pong_task*) test_context_get();
134+
struct pong_task *task = (struct pong_task*) test_context_get_0();
135135

136136
// assert this event is a future read completion
137137
assert(event->event == TEST_EVENT_FUTURE_READ);

tests/runtime-async/async/simple-yield/test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ test_subtask_status_t exports_test_f_callback(test_event_t *event) {
1212
assert(event->event == TEST_EVENT_NONE);
1313
assert(event->waitable == 0);
1414
assert(event->code == 0);
15-
test_yield();
15+
test_thread_yield();
1616
exports_test_f_return();
1717
return TEST_CALLBACK_CODE_EXIT;
1818
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ args = '--rename a:b/i=test --async=-run'
2+
#include <assert.h>
3+
#include <runner.h>
4+
5+
void exports_runner_run()
6+
{
7+
runner_subtask_status_t status = test_f();
8+
assert(RUNNER_SUBTASK_STATE(status) == RUNNER_SUBTASK_STARTED);
9+
runner_subtask_t task = RUNNER_SUBTASK_HANDLE(status);
10+
11+
runner_waitable_set_t set = runner_waitable_set_new();
12+
runner_waitable_join(task, set);
13+
runner_event_t event;
14+
runner_waitable_set_wait(set, &event);
15+
assert(event.event == RUNNER_EVENT_SUBTASK);
16+
assert(event.waitable == task);
17+
assert(event.code == RUNNER_SUBTASK_RETURNED);
18+
runner_waitable_join(task, 0);
19+
runner_waitable_set_drop(set);
20+
}

0 commit comments

Comments
 (0)