Skip to content

Commit

Permalink
Implement WebAssembly threads proposal with interpreter (tetratelabs#…
Browse files Browse the repository at this point in the history
…1460)

Signed-off-by: Anuraag Agrawal <anuraaga@gmail.com>
  • Loading branch information
anuraaga authored May 16, 2023
1 parent cb06b7b commit bc96257
Show file tree
Hide file tree
Showing 103 changed files with 6,255 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/spectest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
spec-version:
- "v1"
- "v2"
- "threads"

steps:
- uses: actions/checkout@v3
Expand All @@ -61,6 +62,7 @@ jobs:
spec-version:
- "v1"
- "v2"
- "threads"

steps:
- uses: actions/checkout@v3
Expand Down
22 changes: 21 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,15 @@ spectest_v2_dir := $(spectest_base_dir)/v2
spectest_v2_testdata_dir := $(spectest_v2_dir)/testdata
# Latest draft state as of Dec 16, 2022.
spec_version_v2 := 1782235239ddebaf2cb079b00fdaa2d2c4dedba3
spectest_threads_dir := $(spectest_base_dir)/threads
spectest_threads_testdata_dir := $(spectest_threads_dir)/testdata
spec_version_threads := cc01bf0d17ba3fb1dc59fb7c5c725838aff18b50

.PHONY: build.spectest
build.spectest:
@$(MAKE) build.spectest.v1
@$(MAKE) build.spectest.v2
@$(MAKE) build.spectest.threads

.PHONY: build.spectest.v1
build.spectest.v1: # Note: wabt by default uses >1.0 features, so wast2json flags might drift as they include more. See WebAssembly/wabt#1878
Expand Down Expand Up @@ -169,9 +173,21 @@ build.spectest.v2: # Note: SIMD cases are placed in the "simd" subdirectory.
wast2json --debug-names $$f; \
done

.PHONY: build.spectest.threads
build.spectest.threads:
@mkdir -p $(spectest_threads_testdata_dir)
@cd $(spectest_threads_testdata_dir) \
&& curl -sSL 'https://api.github.com/repos/WebAssembly/threads/contents/test/core?ref=$(spec_version_threads)' | jq -r '.[]| .download_url' | grep -E "atomic.wast" | xargs -Iurl curl -sJL url -O
# Fix broken CAS spectests
# https://github.com/WebAssembly/threads/issues/195#issuecomment-1318429506
@cd $(spectest_threads_testdata_dir) && patch < ../atomic.wast.patch
@cd $(spectest_threads_testdata_dir) && for f in `find . -name '*.wast'`; do \
wast2json --enable-threads --debug-names $$f; \
done

.PHONY: test
test:
@go test $(go_test_options) $$(go list ./... | grep -vE '$(spectest_v1_dir)|$(spectest_v2_dir)')
@go test $(go_test_options) $$(go list ./... | grep -vE '$(spectest_v1_dir)|$(spectest_v2_dir)|$(spectest_threads_dir)')
@cd internal/version/testdata && go test $(go_test_options) ./...

.PHONY: coverage
Expand All @@ -185,13 +201,17 @@ coverage: ## Generate test coverage
spectest:
@$(MAKE) spectest.v1
@$(MAKE) spectest.v2
@$(MAKE) spectest.threads

spectest.v1:
@go test $(go_test_options) $$(go list ./... | grep $(spectest_v1_dir))

spectest.v2:
@go test $(go_test_options) $$(go list ./... | grep $(spectest_v2_dir))

spectest.threads:
@go test $(go_test_options) $$(go list ./... | grep $(spectest_threads_dir))

golangci_lint_path := $(shell go env GOPATH)/bin/golangci-lint

$(golangci_lint_path):
Expand Down
5 changes: 5 additions & 0 deletions api/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ const (
// Note: The instruction list is too long to enumerate in godoc.
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md
CoreFeatureSIMD

// Update experimental/features.go when adding elements here.
)

// SetEnabled enables or disables the feature or group of features.
Expand Down Expand Up @@ -207,6 +209,9 @@ func featureName(f CoreFeatures) string {
case CoreFeatureSIMD:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md
return "simd"
case CoreFeatureSIMD << 1: // Defined in experimental/features.go
// match https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md
return "threads"
}
return ""
}
14 changes: 14 additions & 0 deletions experimental/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package experimental

import "github.com/tetratelabs/wazero/api"

// CoreFeaturesThreads enables threads instructions ("threads").
//
// # Notes
//
// - This is not yet implemented by default, so you will need to use
// wazero.NewRuntimeConfigInterpreter
// - The instruction list is too long to enumerate in godoc.
// See https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md
// - Atomic operations are guest-only until api.Memory or otherwise expose them to host functions.
const CoreFeaturesThreads = api.CoreFeatureSIMD << 1 // TODO: Implement the compiler engine
94 changes: 94 additions & 0 deletions experimental/features_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package experimental_test

import (
"context"
_ "embed"
"fmt"
"log"
"sync"

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
)

// pthreadWasm was generated by the following:
//
// docker run -it --rm -v `pwd`/testdata:/workspace ghcr.io/webassembly/wasi-sdk:wasi-sdk-20 sh -c '$CC -o /workspace/pthread.wasm /workspace/pthread.c --target=wasm32-wasi-threads --sysroot=/wasi-sysroot -pthread -mexec-model=reactor -Wl,--export=run -Wl,--export=get'
//
// TODO: Use zig cc instead of wasi-sdk to compile when it supports wasm32-wasi-threads
// https://github.com/ziglang/zig/issues/15484
//
//go:embed testdata/pthread.wasm
var pthreadWasm []byte

// This shows how to use a WebAssembly module compiled with the threads feature.
func ExampleCoreFeaturesThreads() {
// Use a default context
ctx := context.Background()

// Threads support is currently only supported with interpreter, so the config
// must explicitly specify it.
cfg := wazero.NewRuntimeConfigInterpreter()

// Threads support must be enabled explicitly in addition to standard V2 features.
cfg = cfg.WithCoreFeatures(api.CoreFeaturesV2 | experimental.CoreFeaturesThreads)

r := wazero.NewRuntimeWithConfig(ctx, cfg)
defer r.Close(ctx)

// Because we are using wasi-sdk to compile the guest, we must initialize WASI.
wasi_snapshot_preview1.MustInstantiate(ctx, r)

mod, err := r.Instantiate(ctx, pthreadWasm)
if err != nil {
log.Panicln(err)
}

// Channel to synchronize start of goroutines before running.
startCh := make(chan struct{})
// Channel to synchronize end of goroutines.
endCh := make(chan struct{})

// Unfortunately, while memory accesses are thread-safe using atomic operations, compilers such
// as LLVM still have global state that is not handled thread-safe, preventing actually making
// concurrent invocations. We go ahead and add a global lock for now until this is resolved.
// TODO: Remove this lock once functions can actually be called concurrently.
var mu sync.Mutex

// We start up 8 goroutines and run for 6000 iterations each. The count should reach
// 48000, at the end, but it would not if threads weren't working!
for i := 0; i < 8; i++ {
go func() {
defer func() { endCh <- struct{}{} }()
<-startCh

mu.Lock()
defer mu.Unlock()

// ExportedFunction must be called within each goroutine to have independent call stacks.
// This incurs some overhead, a sync.Pool can be used to reduce this overhead if neeeded.
fn := mod.ExportedFunction("run")
for i := 0; i < 6000; i++ {
_, err := fn.Call(ctx)
if err != nil {
log.Panicln(err)
}
}
}()
}
for i := 0; i < 8; i++ {
startCh <- struct{}{}
}
for i := 0; i < 8; i++ {
<-endCh
}

res, err := mod.ExportedFunction("get").Call(ctx)
if err != nil {
log.Panicln(err)
}
fmt.Println(res[0])
// Output: 48000
}
18 changes: 18 additions & 0 deletions experimental/testdata/pthread.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <pthread.h>

pthread_mutex_t mutex;
int count = 0;

void run() {
pthread_mutex_lock(&mutex);
count++;
pthread_mutex_unlock(&mutex);
}

int get() {
int res;
pthread_mutex_lock(&mutex);
res = count;
pthread_mutex_unlock(&mutex);
return res;
}
Binary file added experimental/testdata/pthread.wasm
Binary file not shown.
Loading

0 comments on commit bc96257

Please sign in to comment.