Skip to content

WASIP3 Locking/Waiting Primitives#746

Open
TartanLlama wants to merge 30 commits intoWebAssembly:mainfrom
TartanLlama:sy/wasip3-locks
Open

WASIP3 Locking/Waiting Primitives#746
TartanLlama wants to merge 30 commits intoWebAssembly:mainfrom
TartanLlama:sy/wasip3-locks

Conversation

@TartanLlama
Copy link
Contributor

@TartanLlama TartanLlama commented Feb 20, 2026

Includes #744

The first batch of changes for cooperative threading. This PR introduces the core locking and waiting primitives that will be used to implement many of the pthreads features, and reimplements the existing locks used throughout the codebase in terms of them.

  • Implements sched_yield in terms of wasip3_thread_yield
  • Adds a guest-space waitlist implementation in __wait.c consisting of:
    • struct __waitlist_node: a singly-linked list of TIDs
    • __waitlist_wait_on: adds the current thread to the waitlist, allocating the node on the stack
    • __waitlist_wake_(one,all): yields directly to one thread in the waitlist and unsuspends the rest
  • Adds a cooperative lock implementation implemented in terms of the waitlist to __lock.c
    • Possible re-entrance of libc function is somewhat complicated by cooperative threading, because there are three relevant classes of sections of code:
      • Sections that may be executed concurrently
      • Sections that may not be executed concurrently, but that contain no context-switch points (yields, import calls, indirect calls)
      • Sections that may not be executed concurrently, and that do contain context-switch points
    • To reflect this and avoid unnecessary locking/unlocking for sections in the second class, this PR adds a WEAK_(UN)LOCK macro in addition to the existing (UN)LOCK, where the former is a no-op in co-operative threading builds
    • I'm not married to these names, and we may decide that the added complexity is not worth it, but I've left this in for now
  • Existing uses of futexes are rewritten to use the cooperative lock when building for WASIP3

Because these patches do not yet include the ability to spawn new threads, which will require llvm/llvm-project#175800, there is no additional testing added yet. Testing will be added as part of integration of the Open POSIX Test Suite, which will be coming in future PRs.

@TartanLlama
Copy link
Contributor Author

Needs a new release of wasm-component-ld to pull in wit-component changes for intrinsics, PR: bytecodealliance/wasm-component-ld#91

@TartanLlama
Copy link
Contributor Author

TartanLlama commented Feb 21, 2026

Strange, these test failures don't repro for me locally on an M3 Mac

@alexcrichton
Copy link
Collaborator

Maybe it's the local clang version used to compile? I'm able to reproduce locally with stock clang-19 from ubuntu sources on Linux.

Copy link
Collaborator

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

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

I was waiting for CI to be green before digging in much further here, but in retrospect there's no need as I don't think the code will change that much. I've left some comments below but overall this looks all good to me, thanks!

If you need help with CI just lemme know and I can try to help frob things.

set(__wasip2__ ON)
elseif(WASI STREQUAL "p3")
set(__wasip3__ ON)
set(__wasi_cooperative_threads__ ON)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could this be behind a #define/guard/etc for now while we're waiting for wasi-sdk/LLVM/etc to all get updated? That way we could go ahead and land all this in wasi-libc (untested) and follow-up with testing later. My gut is that it shouldn't be too hard to support building with/without coop threads within wasi-libc itself, but if that's too difficult to support then it's fine to wait for a build of LLVM with support for coop threds.

Copy link
Collaborator

Choose a reason for hiding this comment

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

HIgh-level question: how come this folder is changing? I would expect wasi-threads to be unaffected by wasip3 things, so I was a bit surprised to see this file change (and the one below)


void __waitlist_wake_all(struct __waitlist_node **list)
{
struct __waitlist_node **prev = list;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here prev is never modified so I think it could be replaced with direct usage of list?

__unlockfile(f);
return c;
#else
if (a_cas(&f->lock, 0, MAYBE_WAITERS-1)) __lockfile(f);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I didn't expect musl to open-code locks in so many places...

self->stdio_locks = f;
}

#ifdef __wasi_cooperative_threads__
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could the #ifdef here go inside the function signature to avoid duplicating the function signature?

Comment on lines +39 to +52
int self_tid = __pthread_self()->tid;
if (f->lock.owner == self_tid) {
if (f->lockcount == LONG_MAX)
return -1;
f->lockcount++;
return 0;
}

// Try to acquire the lock
if (f->lock.owner != 0)
return -1;

f->lock.owner = self_tid;
f->lockcount = 1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could management/testing of .owner (or maybe a try_lock of some kind) go inside a header? Ideally the details of the lock implementation would be centalized in one *.h and one *.c is my rough thinking here

Comment on lines +9 to +12
// Allow recursive locking
if (f->lock.owner == __pthread_self()->tid)
return 0;

Copy link
Collaborator

Choose a reason for hiding this comment

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

If this allows recursive locking, doesn't that mean that unlocking doesn't work? I'd expect some sort of counter to get increased for a recursive lock here or something similar to that.

Copy link
Collaborator

Choose a reason for hiding this comment

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

(or does musl already handle that case elsewhere via the return value of this function?)

If musl already handles this, a comment similar to below where I'd request that the testing of .owner be moved to some sort of function in the lock header file

Comment on lines +31 to +38
/* The implementation has no yield points, so locks can be no-ops. */
static inline void lock(volatile int *lk)
{
}

static inline void unlock(volatile int *lk)
{
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could this compile down to some small debug-mode-only instrumentation perhaps?

Copy link
Collaborator

Choose a reason for hiding this comment

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

although this file may be unused by wasi-libc given that malloc lives elsewhere...

#define FFINALLOCK(f) __lockfile((f))
#define FLOCK(f) int __need_unlock = __lockfile((f))
#define FUNLOCK(f) do { if (__need_unlock) __unlockfile((f)); } while (0)
#define __STDIO_LOCK_INIT {0, 0}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Aha looks like musl handles my concern below (above in review? who knows...). Not exactly how I'd imagine it being done but hey here it is

#define FLOCK(f) int __need_unlock = __lockfile((f))
#define FUNLOCK(f) do { if (__need_unlock) __unlockfile((f)); } while (0)
#define __STDIO_LOCK_INIT {0, 0}
#define __STDIO_LOCK_RESET(lock) do { (lock)->owner = 0; (lock)->waiters = NULL; } while (0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This might be replacable with *lock = __STDIO_LOCK_INIT given what it's doing, but this is all buried in musl code so seems reasonable either way, happy to defer to you

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.

2 participants