Skip to content

Commit 128c0ea

Browse files
authored
Add internal tests for WASI threads (#1963)
Add internal tests for WASI threads. These tests are run in addition to the ones in the proposal: https://github.com/WebAssembly/wasi-threads/tree/main/test/testsuite. The purpose is to test additional and more complex scenarios.
1 parent 289fc5e commit 128c0ea

36 files changed

Lines changed: 864 additions & 11 deletions

.github/workflows/compilation_on_android_ubuntu.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,10 @@ jobs:
457457
$THREADS_TEST_OPTIONS,
458458
$WASI_TEST_OPTIONS,
459459
]
460+
wasi_sdk_release:
461+
[
462+
"https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-19/wasi-sdk-19.0-linux.tar.gz",
463+
]
460464
llvm_cache_key:
461465
["${{ needs.build_llvm_libraries_on_ubuntu_2004.outputs.cache_key }}"]
462466
exclude:
@@ -493,6 +497,31 @@ jobs:
493497
- name: checkout
494498
uses: actions/checkout@v3
495499

500+
- name: download and install wasi-sdk
501+
if: matrix.test_option == '$WASI_TEST_OPTIONS'
502+
run: |
503+
cd /opt
504+
sudo wget ${{ matrix.wasi_sdk_release }}
505+
sudo tar -xzf wasi-sdk-*.tar.gz
506+
sudo mv wasi-sdk-19.0 wasi-sdk
507+
508+
- name: build wasi-libc (needed for wasi-threads)
509+
if: matrix.test_option == '$WASI_TEST_OPTIONS'
510+
run: |
511+
mkdir wasi-libc
512+
cd wasi-libc
513+
git init
514+
# "Rename thread_spawn import" commit on main branch
515+
git fetch https://github.com/WebAssembly/wasi-libc \
516+
8f5275796a82f8ecfd0833a4f3f444fa37ed4546
517+
git checkout FETCH_HEAD
518+
make \
519+
AR=/opt/wasi-sdk/bin/llvm-ar \
520+
NM=/opt/wasi-sdk/bin/llvm-nm \
521+
CC=/opt/wasi-sdk/bin/clang \
522+
THREAD_MODEL=posix
523+
working-directory: core/deps
524+
496525
- name: set env variable(if llvm are used)
497526
if: matrix.running_mode == 'aot' || matrix.running_mode == 'jit' || matrix.running_mode == 'multi-tier-jit'
498527
run: echo "USE_LLVM=true" >> $GITHUB_ENV
@@ -526,6 +555,11 @@ jobs:
526555
if: matrix.running_mode == 'aot' && matrix.test_option == '$WASI_TEST_OPTIONS'
527556
run: sudo apt-get update && sudo apt install -y jq
528557

558+
- name: Build WASI thread tests
559+
if: matrix.test_option == '$WASI_TEST_OPTIONS'
560+
run: WASI_SYSROOT=../../../../../core/deps/wasi-libc/sysroot bash build.sh
561+
working-directory: ./core/iwasm/libraries/lib-wasi-threads/test/
562+
529563
- name: run tests
530564
timeout-minutes: 10
531565
run: ./test_wamr.sh ${{ matrix.test_option }} -t ${{ matrix.running_mode }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
core/deps/**
1414
core/shared/mem-alloc/tlsf
1515
core/app-framework/wgl
16+
core/iwasm/libraries/lib-wasi-threads/test/*.wasm
1617

1718
wamr-sdk/out/
1819
wamr-sdk/runtime/build_runtime_sdk/
@@ -35,3 +36,5 @@ tests/benchmarks/coremark/coremark*
3536

3637
samples/workload/include/**
3738
!samples/workload/include/.gitkeep
39+
40+
# core/iwasm/libraries/wasi-threads
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/bash
2+
3+
#
4+
# Copyright (C) 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
#
7+
8+
CC=${CC:=/opt/wasi-sdk/bin/clang}
9+
WASI_SYSROOT=${WASI_SYSROOT:=~/dev/wasi-libc/sysroot}
10+
WAMR_DIR=../../../../..
11+
12+
for test_c in *.c; do
13+
test_wasm="$(basename $test_c .c).wasm"
14+
15+
echo "Compiling $test_c to $test_wasm"
16+
$CC \
17+
--sysroot $WASI_SYSROOT \
18+
-target wasm32-wasi-threads \
19+
-pthread -ftls-model=local-exec \
20+
-z stack-size=32768 \
21+
-Wl,--export=__heap_base \
22+
-Wl,--export=__data_end \
23+
-Wl,--shared-memory,--max-memory=1966080 \
24+
-Wl,--export=wasi_thread_start \
25+
-Wl,--export=malloc \
26+
-Wl,--export=free \
27+
-I $WAMR_DIR/samples/wasi-threads/wasm-apps \
28+
$WAMR_DIR/samples/wasi-threads/wasm-apps/wasi_thread_start.S \
29+
$test_c -o $test_wasm
30+
done
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright (C) 2022 Amazon.com Inc. or its affiliates. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
*/
5+
6+
#include <stdlib.h>
7+
#include <stdio.h>
8+
#include <assert.h>
9+
#include <pthread.h>
10+
#include <stdbool.h>
11+
#include <unistd.h>
12+
13+
#include "wasi_thread_start.h"
14+
15+
typedef enum {
16+
BLOCKING_TASK_BUSY_WAIT,
17+
BLOCKING_TASK_ATOMIC_WAIT,
18+
BLOCKING_TASK_POLL_ONEOFF
19+
} blocking_task_type_t;
20+
21+
/* Parameter to change test behavior */
22+
static bool termination_by_trap;
23+
static bool termination_in_main_thread;
24+
static blocking_task_type_t blocking_task_type;
25+
26+
#define TIMEOUT_SECONDS 10ll
27+
#define NUM_THREADS 3
28+
static pthread_barrier_t barrier;
29+
30+
typedef struct {
31+
start_args_t base;
32+
bool throw_exception;
33+
} shared_t;
34+
35+
void
36+
run_long_task()
37+
{
38+
if (blocking_task_type == BLOCKING_TASK_BUSY_WAIT) {
39+
for (int i = 0; i < TIMEOUT_SECONDS; i++)
40+
sleep(1);
41+
}
42+
else if (blocking_task_type == BLOCKING_TASK_ATOMIC_WAIT) {
43+
__builtin_wasm_memory_atomic_wait32(
44+
0, 0, TIMEOUT_SECONDS * 1000 * 1000 * 1000);
45+
}
46+
else {
47+
sleep(TIMEOUT_SECONDS);
48+
}
49+
}
50+
51+
void
52+
start_job()
53+
{
54+
/* Wait for all threads (including the main thread) to be ready */
55+
pthread_barrier_wait(&barrier);
56+
run_long_task(); /* Task to be interrupted */
57+
assert(false && "Thread termination test failed");
58+
}
59+
60+
void
61+
terminate_process()
62+
{
63+
/* Wait for all threads (including the main thread) to be ready */
64+
pthread_barrier_wait(&barrier);
65+
66+
if (termination_by_trap)
67+
__builtin_trap();
68+
else
69+
__wasi_proc_exit(33);
70+
}
71+
72+
void
73+
__wasi_thread_start_C(int thread_id, int *start_arg)
74+
{
75+
shared_t *data = (shared_t *)start_arg;
76+
77+
if (data->throw_exception) {
78+
terminate_process();
79+
}
80+
else {
81+
start_job();
82+
}
83+
}
84+
85+
void
86+
test_termination(bool trap, bool main, blocking_task_type_t task_type)
87+
{
88+
termination_by_trap = trap;
89+
termination_in_main_thread = main;
90+
blocking_task_type = task_type;
91+
92+
int thread_id = -1, i;
93+
shared_t data[NUM_THREADS] = { 0 };
94+
assert(pthread_barrier_init(&barrier, NULL, NUM_THREADS + 1) == 0
95+
&& "Failed to init barrier");
96+
97+
for (i = 0; i < NUM_THREADS; i++) {
98+
/* No graceful memory free to simplify the test */
99+
assert(start_args_init(&data[i].base)
100+
&& "Failed to allocate thread's stack");
101+
}
102+
103+
/* Create a thread that forces termination through trap or `proc_exit` */
104+
data[0].throw_exception = !termination_in_main_thread;
105+
thread_id = __wasi_thread_spawn(&data[0]);
106+
assert(thread_id > 0 && "Failed to create thread");
107+
108+
/* Create two additional threads to test exception propagation */
109+
data[1].throw_exception = false;
110+
thread_id = __wasi_thread_spawn(&data[1]);
111+
assert(thread_id > 0 && "Failed to create thread");
112+
data[2].throw_exception = false;
113+
thread_id = __wasi_thread_spawn(&data[2]);
114+
assert(thread_id > 0 && "Failed to create thread");
115+
116+
if (termination_in_main_thread) {
117+
terminate_process();
118+
}
119+
else {
120+
start_job();
121+
}
122+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright (C) 2023 Amazon.com Inc. or its affiliates. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
*/
5+
6+
#ifndef __wasi__
7+
#error This example only compiles to WASM/WASI target
8+
#endif
9+
10+
#include <stdlib.h>
11+
#include <stdio.h>
12+
#include <assert.h>
13+
#include <stdbool.h>
14+
15+
#include "wasi_thread_start.h"
16+
17+
enum CONSTANTS {
18+
MAX_NUM_THREADS = 4, /* Should be the same as "--max-threads" */
19+
NUM_RETRY = 5,
20+
SECOND = 1000 * 1000 * 1000, /* 1 second */
21+
TIMEOUT = 10LL * SECOND
22+
};
23+
24+
int g_count = 0;
25+
26+
typedef struct {
27+
start_args_t base;
28+
int th_ready;
29+
int th_continue;
30+
int th_done;
31+
bool no_ops;
32+
} shared_t;
33+
34+
void
35+
__wasi_thread_start_C(int thread_id, int *start_arg)
36+
{
37+
shared_t *data = (shared_t *)start_arg;
38+
39+
if (data->no_ops) {
40+
__builtin_wasm_memory_atomic_wait32(NULL, 0, 2 * SECOND);
41+
return;
42+
}
43+
44+
__atomic_store_n(&data->th_ready, 1, __ATOMIC_SEQ_CST);
45+
__builtin_wasm_memory_atomic_notify(&data->th_ready, 1);
46+
47+
if (__builtin_wasm_memory_atomic_wait32(&data->th_continue, 0, TIMEOUT)
48+
== 2) {
49+
assert(false && "Wait should not time out");
50+
}
51+
52+
__atomic_fetch_add(&g_count, 1, __ATOMIC_SEQ_CST);
53+
54+
__atomic_store_n(&data->th_done, 1, __ATOMIC_SEQ_CST);
55+
__builtin_wasm_memory_atomic_notify(&data->th_done, 1);
56+
}
57+
58+
int
59+
main(int argc, char **argv)
60+
{
61+
shared_t data[MAX_NUM_THREADS] = { 0 };
62+
int thread_ids[MAX_NUM_THREADS];
63+
64+
for (int i = 0; i < MAX_NUM_THREADS; i++) {
65+
assert(start_args_init(&data[i].base));
66+
thread_ids[i] = __wasi_thread_spawn(&data[i]);
67+
printf("Thread created with id=%d\n", thread_ids[i]);
68+
assert(thread_ids[i] > 0 && "Thread creation failed");
69+
70+
for (int j = 0; j < i; j++) {
71+
assert(thread_ids[i] != thread_ids[j] && "Duplicated TIDs");
72+
}
73+
74+
if (__builtin_wasm_memory_atomic_wait32(&data[i].th_ready, 0, TIMEOUT)
75+
== 2) {
76+
assert(false && "Wait should not time out");
77+
}
78+
}
79+
80+
printf("Attempt to create thread when not possible\n");
81+
shared_t data_fail = { 0 };
82+
assert(start_args_init(&data_fail.base));
83+
int thread_id = __wasi_thread_spawn(&data_fail);
84+
start_args_deinit(&data_fail.base);
85+
assert(thread_id < 0 && "Thread creation should fail");
86+
87+
printf("Unlock created threads\n");
88+
for (int i = 0; i < MAX_NUM_THREADS; i++) {
89+
__atomic_store_n(&data[i].th_continue, 1, __ATOMIC_SEQ_CST);
90+
__builtin_wasm_memory_atomic_notify(&data[i].th_continue, 1);
91+
}
92+
93+
printf("Wait for threads to finish\n");
94+
for (int i = 0; i < MAX_NUM_THREADS; i++) {
95+
if (__builtin_wasm_memory_atomic_wait32(&data[i].th_done, 0, TIMEOUT)
96+
== 2) {
97+
assert(false && "Wait should not time out");
98+
}
99+
100+
start_args_deinit(&data[i].base);
101+
}
102+
103+
printf("Value of count after update: %d\n", g_count);
104+
assert(g_count == (MAX_NUM_THREADS)
105+
&& "Global count not updated correctly");
106+
107+
/* --------------------------------------------------- */
108+
109+
printf("Create new threads without waiting from them to finish\n");
110+
shared_t data_no_join[MAX_NUM_THREADS] = { 0 };
111+
for (int i = 0; i < MAX_NUM_THREADS; i++) {
112+
/* No graceful memory free to simplify the test */
113+
assert(start_args_init(&data_no_join[i].base));
114+
data_no_join[i].no_ops = true;
115+
116+
int thread_id = -1;
117+
for (int j = 0; j < NUM_RETRY && thread_id < 0; j++) {
118+
thread_id = __wasi_thread_spawn(&data_no_join[i]);
119+
if (thread_id < 0)
120+
__builtin_wasm_memory_atomic_wait32(NULL, 0, SECOND);
121+
}
122+
123+
printf("Thread created with id=%d\n", thread_id);
124+
assert(thread_id > 0 && "Thread creation should succeed");
125+
}
126+
127+
return EXIT_SUCCESS;
128+
}

0 commit comments

Comments
 (0)