CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
thread_pool.h
Go to the documentation of this file.
1// Copyright Microsoft and CHERIoT Contributors.
2// SPDX-License-Identifier: MIT
3
4#pragma once
5#include <__cheri_sealed.h>
6#include <cdefs.h>
7
8/**
9 * \file
10 *
11 * APIs for an example thread pool. This does not provide any availability
12 * guarantees (work dispatched to it can block a thread indefinitely) so this
13 * should be used with care.
14 *
15 * The thread pool exposed by this header is implemented in the `thread_pool`
16 * compartment, which must be linked to the final firmware image.
17 */
18
19/**
20 * The type of a thread-pool callback. This is a CHERI callback so that it can
21 * run in the compartment that schedule the work to run, but a different
22 * thread.
23 */
24typedef __cheri_callback void (*ThreadPoolCallback)(CHERI_SEALED(void *));
25
26/**
27 * Message to a thread pool. This encapsulates a simple closure that will be
28 * invoked in the next available thread in a thread pool.
29 */
31{
32 /**
33 * The function pointer that will be called in the thread pool.
34 */
36 /**
37 * The data associated with the asynchronous invocation.
38 */
39 CHERI_SEALED(void *) data;
40};
41
42__BEGIN_DECLS
43
44/**
45 * Invoke a function that takes a `void*` argument in another thread. The
46 * function will be invoked with `data` as the argument. The `data` argument
47 * must be sealed.
48 *
49 * This can block indefinitely until the thread pool is able to process a
50 * message.
51 *
52 * FIXME: We should add a non-blocking variant of this.
53 *
54 * Returns 0 on success, -EINVAL if either of the arguments are invalid.
55 */
56int __cheri_compartment("thread_pool")
57 thread_pool_async(ThreadPoolCallback fn, CHERI_SEALED(void *) data);
58
59/**
60 * Run a thread pool. This does not return, despite the claimed type, and can
61 * be used as a thread entry point.
62 */
63int __cheri_compartment("thread_pool") thread_pool_run(void);
64__END_DECLS
65
66#ifdef __cplusplus
67# include <stdlib.h>
68# include <token.h>
69# include <type_traits>
70# include <cheri.hh>
71/**
72 * For C++ programmers, we provide some more user-friendly wrappers.
73 */
74namespace thread_pool
75{
76 /**
77 * Implementation details, should not be used outside of this header.
78 */
79 namespace detail
80 {
81 /**
82 * Helper that generates a different sealing key per type using the
83 * allocator's token mechanism.
84 */
85 template<typename T>
87 {
88 static TokenKey key = token_key_new();
89 return key;
90 }
91
92 /**
93 * Helper that provides a callback function for invoking and then
94 * freeing a sealed heap-allocated lambda. The lambda is sealed with
95 * the software-defined sealing key constructed by the
96 * `sealing_key_for_type` helper.
97 */
98 template<typename T>
99 __cheri_callback void wrap_callback_lambda(CHERI_SEALED(void *) rawFn)
100 {
101 auto key = sealing_key_for_type<T>();
102 auto fn = token_unseal(
103 key, Sealed<T>(static_cast<CHERI_SEALED(T *)>(rawFn)));
104 if (fn == nullptr)
105 {
106 return;
107 }
108 (*fn)();
109 /*
110 * This fails only if the thread pool runner compartment can't make
111 * cross-compartment calls to the allocator at all, since we're
112 * in its initial trusted activation frame and near the beginning
113 * (highest address) of its stack.
114 *
115 * Well, mostly. It can also fail if the allocator has stack usage
116 * checks compiled in and we have forgotten to bump that value.
117 */
118 (void)token_obj_destroy(MALLOC_CAPABILITY, key, rawFn);
119 }
120
121 /**
122 * Helper to wrap pure C function (including a stateless lambda) as a
123 * callback.
124 */
125 template<typename Fn>
126 __cheri_callback void wrap_callback_function(CHERI_SEALED(void *))
127 {
128 // C++ objects are guaranteed to have unique addresses and so a
129 // zero-sized object is actually 1 byte and using `sizeof(Fn) == 1`
130 // would not let us tell the difference between empty lambdas and
131 // ones with a one-byte capture. To ensure that this object is
132 // really stateless, we make it a field of another type, marking it
133 // as not requiring a unique address, and then check that the
134 // offset of the following field is 0.
135 struct Test
136 {
137 // The stateless callable object
138 [[no_unique_address]] Fn f;
139 // An field that can share the same address as `f` if `f` is
140 // truly stateless.
141 char b;
142 };
143 static_assert(offsetof(Test, b) == 0,
144 "Stateless callable object is not stateless");
145 // Invoke an empty instance of this stateless callable object.
146 Fn{}();
147 }
148
149 } // namespace detail
150
151 /**
152 * Asynchronously invoke a lambda. This moves the lambda to the heap and
153 * passes it to the thread pool's queue. If the lambda copies any stack
154 * objects by reference then the copy will fail.
155 *
156 * Returns 0 on success, or compartment-call failures (ENOTENOUGHSTACK,
157 * ENOTENOUGHTRUSTEDSTACK) if the thread pool cannot be invoked.
158 */
159 template<typename T>
160 int async(T &&lambda)
161 {
162 // If this is a stateless function, just send a callback function
163 // pointer, don't copy zero bytes of state to the heap.
164 if constexpr (std::is_convertible_v<T, void (*)()>)
165 {
166 return thread_pool_async(
167 &detail::wrap_callback_function<std::remove_cvref_t<T>>, nullptr);
168 }
169 else
170 {
171 // If this is a stateful lambda, move it to the heap, create a
172 // callback function that will invoke it and free it, and send a
173 // pointer to the wrapper and the heap-allocated object.
174 void *buffer;
175 using LambdaType = std::remove_reference_t<decltype(lambda)>;
176 // Allocate a new sealed object with a key that is unique to this
177 // type.
179 CHERI_SEALED(void *)
181 &t,
182 MALLOC_CAPABILITY,
184 sizeof(lambda),
185 &buffer);
186 /*
187 * Copy the lambda into the new allocation.
188 *
189 * If the above allocation has failed, this will trap.
190 *
191 * Note: We silence a warning here because we *do* want to
192 * explicitly move, not forward.
193 */
194 T *copy = new (buffer) T(
195 std::move(lambda)); // NOLINT(bugprone-move-forwarding-reference)
196 // Create the wrapper that will unseal and invoke the lambda.
197 ThreadPoolCallback invoke =
199 // Dispatch it.
200 return thread_pool_async(invoke, sealed);
201 }
202 }
203} // namespace thread_pool
204
205#endif
C++ helpers for operating on capabilities.
Helper template for representing a sealed capability created by the allocator's token API.
Definition token.h:201
Implementation details, should not be used outside of this header.
Definition thread_pool.h:80
TokenKey sealing_key_for_type()
Helper that generates a different sealing key per type using the allocator's token mechanism.
Definition thread_pool.h:86
__cheri_callback void wrap_callback_lambda(CHERI_SEALED(void *) rawFn)
Helper that provides a callback function for invoking and then freeing a sealed heap-allocated lambda...
Definition thread_pool.h:99
__cheri_callback void wrap_callback_function(CHERI_SEALED(void *))
Helper to wrap pure C function (including a stateless lambda) as a callback.
For C++ programmers, we provide some more user-friendly wrappers.
Definition thread_pool.h:75
int async(T &&lambda)
Asynchronously invoke a lambda.
Message to a thread pool.
Definition thread_pool.h:31
ThreadPoolCallback invoke
The function pointer that will be called in the thread pool.
Definition thread_pool.h:35
CHERI_SEALED(void *) data
The data associated with the asynchronous invocation.
Structure representing a timeout.
Definition timeout.h:47
int thread_pool_async(ThreadPoolCallback fn, CHERI_SEALED(void *) data)
Invoke a function that takes a void* argument in another thread.
__cheri_callback void(* ThreadPoolCallback)(CHERI_SEALED(void *))
The type of a thread-pool callback.
Definition thread_pool.h:24
int thread_pool_run(void)
Run a thread pool.
static const Ticks UnlimitedTimeout
Value indicating an unbounded timeout for use with Timeout.
Definition timeout.h:22
APIs for dealing with type-safe opaque handles (sealed objects).
T * token_unseal(TokenKey key, Sealed< T > sealed)
Type-safe helper that unseal a Sealed<T> and returns a T*.
Definition token.h:287
token_sealed_unsealed_alloc(TimeoutArgument timeout, AllocatorCapability heapCapability, TokenKey key, size_t sz, void **unsealed)
Allocate a new object with size sz.
TokenKey token_key_new(void)
Create a new sealing key.
int token_obj_destroy(AllocatorCapability heapCapability, TokenKey, CHERI_SEALED(void *))
Destroy the object given its key, freeing memory.
struct TokenKeyType * TokenKey
Sealing keys in CHERI are represented as capabilities.
Definition token.h:30