Skip to content

[libc] Mutex implementation for single-threaded baremetal #145358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

saturn691
Copy link
Contributor

@saturn691 saturn691 commented Jun 23, 2025

As the first part of #145349, we need a Mutex, to allow atexit to work. As part of HermeticTestUtils.cpp, there is a reference to atexit(), which eventually instantiates an instance of a Mutex. Most of the implementation is copied from libc/src/__support/threads/gpu/mutex.h.

Later, when the threading API is more complete, we can probably add an option to support multithreading (or set it as the default), but having single-threading (in tandem) is in line with other libraries for embedded devices. For my use case, this implementation suffices.

@llvmbot llvmbot added the libc label Jun 23, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 23, 2025

@llvm/pr-subscribers-backend-amdgpu

@llvm/pr-subscribers-libc

Author: William Huynh (saturn691)

Changes

In order to add, #145349, we need a Mutex, to allow atexit to work.


Full diff: https://github.com/llvm/llvm-project/pull/145358.diff

3 Files Affected:

  • (added) libc/src/__support/threads/baremetal/CMakeLists.txt (+5)
  • (added) libc/src/__support/threads/baremetal/mutex.h (+32)
  • (modified) libc/src/__support/threads/mutex.h (+2)
diff --git a/libc/src/__support/threads/baremetal/CMakeLists.txt b/libc/src/__support/threads/baremetal/CMakeLists.txt
new file mode 100644
index 0000000000000..ea89feb0c5c68
--- /dev/null
+++ b/libc/src/__support/threads/baremetal/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_header_library(
+  mutex
+  HDRS
+    mutex.h
+)
diff --git a/libc/src/__support/threads/baremetal/mutex.h b/libc/src/__support/threads/baremetal/mutex.h
new file mode 100644
index 0000000000000..77a0b61ea9f5b
--- /dev/null
+++ b/libc/src/__support/threads/baremetal/mutex.h
@@ -0,0 +1,32 @@
+//===--- Implementation of a mutex class for baremetal ----------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_BAREMETAL_MUTEX_H
+#define LLVM_LIBC_SRC___SUPPORT_THREADS_BAREMETAL_MUTEX_H
+
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/mutex_common.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+/// Implementation of a simple passthrough mutex which guards nothing. For
+/// single threaded processors, this is the implementation.
+struct Mutex {
+  LIBC_INLINE constexpr Mutex(bool, bool, bool, bool) {}
+
+  LIBC_INLINE MutexError lock() { return MutexError::NONE; }
+  LIBC_INLINE MutexError unlock() { return MutexError::NONE; }
+  LIBC_INLINE MutexError reset() { return MutexError::NONE; }
+};
+
+// TODO: add multithreading support here
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_BAREMETAL_MUTEX_H
diff --git a/libc/src/__support/threads/mutex.h b/libc/src/__support/threads/mutex.h
index 392b38984dc0a..a600e2ff017f5 100644
--- a/libc/src/__support/threads/mutex.h
+++ b/libc/src/__support/threads/mutex.h
@@ -41,6 +41,8 @@
 #include "src/__support/threads/linux/mutex.h"
 #elif defined(LIBC_TARGET_ARCH_IS_GPU)
 #include "src/__support/threads/gpu/mutex.h"
+#elif defined(__ELF__)
+#include "src/__support/threads/baremetal/mutex.h"
 #endif // __linux__
 
 #endif // LLVM_LIBC_SRC___SUPPORT_THREADS_MUTEX_H

@jhuber6
Copy link
Contributor

jhuber6 commented Jun 23, 2025

I've been considering if it's possible to just make atexit lock free. it would be pretty easy if we defined what happens when one thread calls atexit() while someone is consuming it. Easy mode is that we ignore any attempt to modify after exit is already happening. Hard mode is allowing it to interject and calling it as well.

@mysterymath
Copy link
Contributor

I've been considering if it's possible to just make atexit lock free. it would be pretty easy if we defined what happens when one thread calls atexit() while someone is consuming it. Easy mode is that we ignore any attempt to modify after exit is already happening. Hard mode is allowing it to interject and calling it as well.

That would conflict with the C standard, right?

@jhuber6
Copy link
Contributor

jhuber6 commented Jun 23, 2025

I've been considering if it's possible to just make atexit lock free. it would be pretty easy if we defined what happens when one thread calls atexit() while someone is consuming it. Easy mode is that we ignore any attempt to modify after exit is already happening. Hard mode is allowing it to interject and calling it as well.

That would conflict with the C standard, right?

7.24.4.2.2
The atexit function registers the function pointed to by func, to be called without arguments at
normal program termination.363) It is unspecified whether a call to the atexit function that does
not happen before the exit function is called will succeed. 

I think glibc does a stack that allows this. Possible we'd want to be compatible with that. We currently aren't, because we just lock the interface... Doesn't that mean that if an atexit handler calls atexit it'll deadlock?

@mysterymath
Copy link
Contributor

mysterymath commented Jun 23, 2025

7.24.4.2.2
The atexit function registers the function pointed to by func, to be called without arguments at
normal program termination.363) It is unspecified whether a call to the atexit function that does
not happen before the exit function is called will succeed. 

Whoa, I'm out of date! That's interesting; C11 doesn't have the second sentence, which would ostensibly put it in scope for 7.1.4 (in the draft): Implementations may share their own internal objects between threads if the objects are not visible to users and are protected against data races.

Do we have a go-to policy for cases in libc/libc++ where the standard loosens its requirements from version to version? (Presuming that I'm interpreting things reasonably and that happened here; I haven't looked into this very far. Need to find the rationale doc for that change.)

@mysterymath
Copy link
Contributor

We currently aren't, because we just lock the interface... Doesn't that mean that if an atexit handler calls atexit it'll deadlock?

It shouldn't; the implementation explicitly unlocks the stack before calling a handler:

handler_list_mtx.unlock();

@petrhosek
Copy link
Member

The main concern I have with this change is that it furthers the notion of baremetal as a singular platform whereas in practice there is a diverse set of baremetal platforms. For that reason, I'm starting to consider the existence of baremetal implementation subdirectories in LLVM libc to be an antipattern and I think we should aim to eliminate these directories and baremetal should be just a build configuration.

Rather than introducing threads/baremetal/mutex.h, I think we should introduce a threading mode configuration option akin to what we've done for errno. That is, we would introduce LIBC_CONF_THREAD_MODE with values such as:

  • LIBC_THREAD_MODE_SINGLE: this would be the implementation in this PR,
  • LIBC_THREAD_MODE_EXTERNAL: we could make it a no-op or #error for now,
  • LIBC_THREAD_MODE_PLATFORM: this would be the platform specific implementation for platforms such as Linux.

For baremetal build configuration we would default to LIBC_THREAD_MODE_SINGLE.

I don't expect this to be significantly more complicated than the current PR, but it would more flexible and there might be other build configurations beyond baremetal where LIBC_THREAD_MODE_SINGLE might be useful.

@saturn691 saturn691 force-pushed the mutex branch 4 times, most recently from 35ce1b7 to d7d0433 Compare June 24, 2025 13:06
@saturn691
Copy link
Contributor Author

saturn691 commented Jun 24, 2025

  1. Extremely sorry for the amount of force-pushes, I don't have access to some platforms on my host machine and am using the CI to test that I haven't broken anything (I have tested the baremetal configuration downstream).
  2. Totally agree with your approach, I think it makes a lot of sense here, where we can share the implementation with the GPU, however, I still think in other places it makes a bit more sense, e.g. src/time/baremetal. I think overusing this build configuration will make the codebase harder to read, compared to the "switch-case" like nature of keeping the baremetal folder.

On further thought, I wish to share some of the logic for errno and mutexes, in the sense that if I choose single threading, I want to get the single-threaded errno implementation (currently I use the external interface and it gets messy). We can discuss this another time, as this is outside the scope of this PR.

@saturn691 saturn691 requested a review from jhuber6 July 3, 2025 09:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants