Skip to content

Commit 7f3db7f

Browse files
committed
[Concurrency] Have future functions write their results directly.
Introduce `FutureAsyncContext` to line up with the async context formed by IR generation for the type `<T> () async throws -> T`. When allocating a future task, set up the context with the address of the future's storage for the successful result and null out the error result, so the caller will directly fill in the result. This eliminates a bunch of extra complexity and a copy.
1 parent 19d9e0f commit 7f3db7f

File tree

4 files changed

+41
-62
lines changed

4 files changed

+41
-62
lines changed

include/swift/ABI/Task.h

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -297,23 +297,15 @@ class AsyncTask : public HeapObject, public Job {
297297
/// The type of the result that will be produced by the future.
298298
const Metadata *resultType;
299299

300-
/// The offset of the result in the initial asynchronous context.
301-
unsigned resultOffset;
302-
303-
/// The offset of the error in the initial asynchronous context.
304-
unsigned errorOffset;
305-
306300
// Trailing storage for the result itself. The storage will be uninitialized,
307301
// contain an instance of \c resultType, or contaon an an \c Error.
308302

309303
friend class AsyncTask;
310304

311305
public:
312-
FutureFragment(
313-
const Metadata *resultType, size_t resultOffset, size_t errorOffset)
306+
explicit FutureFragment(const Metadata *resultType)
314307
: waitQueue(WaitQueueItem::get(Status::Executing, nullptr)),
315-
resultType(resultType), resultOffset(resultOffset),
316-
errorOffset(errorOffset) { }
308+
resultType(resultType) { }
317309

318310
/// Destroy the storage associated with the future.
319311
void destroy();
@@ -334,14 +326,15 @@ class AsyncTask : public HeapObject, public Job {
334326
/// fragment.
335327
static size_t storageOffset(const Metadata *resultType) {
336328
size_t offset = sizeof(FutureFragment);
337-
size_t alignment = resultType->vw_alignment();
329+
size_t alignment = std::max(resultType->vw_alignment(), alignof(void *));
338330
return (offset + alignment - 1) & ~(alignment - 1);
339331
}
340332

341333
/// Determine the size of the future fragment given a particular future
342334
/// result type.
343335
static size_t fragmentSize(const Metadata *resultType) {
344-
return storageOffset(resultType) + resultType->vw_size();
336+
return storageOffset(resultType) +
337+
std::max(resultType->vw_size(), sizeof(void *));
345338
}
346339
};
347340

@@ -465,6 +458,19 @@ class YieldingAsyncContext : public AsyncContext {
465458
}
466459
};
467460

461+
/// An asynchronous context within a task that describes a general "Future".
462+
/// task.
463+
///
464+
/// This type matches the ABI of a function `<T> () async throws -> T`, which
465+
/// is used to describe futures.
466+
class FutureAsyncContext : public AsyncContext {
467+
public:
468+
SwiftError *errorResult = nullptr;
469+
OpaqueValue *indirectResult;
470+
471+
using AsyncContext::AsyncContext;
472+
};
473+
468474
} // end namespace swift
469475

470476
#endif

include/swift/Runtime/Concurrency.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,14 @@ AsyncTaskAndContext swift_task_create_f(JobFlags flags,
6565
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
6666
AsyncTaskAndContext swift_task_create_future(
6767
JobFlags flags, AsyncTask *parent, const Metadata *futureResultType,
68-
const AsyncFunctionPointer<void()> *function,
69-
size_t resultOffset, size_t errorOffset);
68+
const AsyncFunctionPointer<void()> *function);
7069

7170
/// Create a task object with a future which will run the given
7271
/// function.
7372
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
7473
AsyncTaskAndContext swift_task_create_future_f(
7574
JobFlags flags, AsyncTask *parent, const Metadata *futureResultType,
76-
AsyncFunctionType<void()> *function, size_t initialContextSize,
77-
size_t resultOffset, size_t errorOffset);
75+
AsyncFunctionType<void()> *function, size_t initialContextSize);
7876

7977
/// Allocate memory in a task.
8078
///

stdlib/public/Concurrency/Task.cpp

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -79,27 +79,13 @@ void AsyncTask::completeFuture(AsyncContext *context, ExecutorRef executor) {
7979

8080
assert(isFuture());
8181
auto fragment = futureFragment();
82-
auto storagePtr = fragment->getStoragePtr();
8382

84-
// Check for an error.
83+
// If an error was thrown, save it in the future fragment.
84+
auto futureContext = static_cast<FutureAsyncContext *>(context);
8585
bool hadErrorResult = false;
86-
if (unsigned errorOffset = fragment->errorOffset) {
87-
// Find the error object in the context.
88-
auto errorPtrPtr = reinterpret_cast<char *>(context) + errorOffset;
89-
OpaqueValue *errorObject = *reinterpret_cast<OpaqueValue **>(errorPtrPtr);
90-
91-
// If there is an error, take it and we're done.
92-
if (errorObject) {
93-
*reinterpret_cast<OpaqueValue **>(storagePtr) = errorObject;
94-
hadErrorResult = true;
95-
}
96-
}
97-
98-
if (!hadErrorResult) {
99-
// Take the success value.
100-
auto resultPtr = reinterpret_cast<OpaqueValue *>(
101-
reinterpret_cast<char *>(context) + fragment->resultOffset);
102-
fragment->resultType->vw_initializeWithTake(storagePtr, resultPtr);
86+
if (auto errorObject = futureContext->errorResult) {
87+
fragment->getError() = errorObject;
88+
hadErrorResult = true;
10389
}
10490

10591
// Update the status to signal completion.
@@ -197,24 +183,22 @@ swift::swift_task_create_f(JobFlags flags, AsyncTask *parent,
197183
AsyncFunctionType<void()> *function,
198184
size_t initialContextSize) {
199185
return swift_task_create_future_f(
200-
flags, parent, nullptr, function, initialContextSize, 0, 0);
186+
flags, parent, nullptr, function, initialContextSize);
201187
}
202188

203189
AsyncTaskAndContext swift::swift_task_create_future(
204190
JobFlags flags, AsyncTask *parent, const Metadata *futureResultType,
205-
const AsyncFunctionPointer<void()> *function,
206-
size_t resultOffset, size_t errorOffset) {
191+
const AsyncFunctionPointer<void()> *function) {
207192
return swift_task_create_future_f(
208193
flags, parent, futureResultType, function->Function.get(),
209-
function->ExpectedContextSize, resultOffset, errorOffset);
194+
function->ExpectedContextSize);
210195
}
211196

212197
AsyncTaskAndContext swift::swift_task_create_future_f(
213198
JobFlags flags, AsyncTask *parent, const Metadata *futureResultType,
214-
AsyncFunctionType<void()> *function, size_t initialContextSize,
215-
size_t resultOffset, size_t errorOffset) {
199+
AsyncFunctionType<void()> *function, size_t initialContextSize) {
200+
assert((futureResultType != nullptr) == flags.task_isFuture());
216201
assert((futureResultType != nullptr) == flags.task_isFuture());
217-
assert((resultOffset != 0) == flags.task_isFuture());
218202
assert((parent != nullptr) == flags.task_isChildTask());
219203

220204
// Figure out the size of the header.
@@ -258,14 +242,13 @@ AsyncTaskAndContext swift::swift_task_create_future_f(
258242
// Initialize the future fragment if applicable.
259243
if (futureResultType) {
260244
auto futureFragment = task->futureFragment();
261-
new (futureFragment) FutureFragment(
262-
futureResultType, resultOffset, errorOffset);
245+
new (futureFragment) FutureFragment(futureResultType);
263246

264-
// Zero out the error, so the task does not need to do it.
265-
if (errorOffset) {
266-
auto errorPtrPtr = reinterpret_cast<char *>(initialContext) + errorOffset;
267-
*reinterpret_cast<OpaqueValue **>(errorPtrPtr) = nullptr;
268-
}
247+
// Set up the context for the future so there is no error, and a successful
248+
// result will be written into the future fragment's storage.
249+
auto futureContext = static_cast<FutureAsyncContext *>(initialContext);
250+
futureContext->errorResult = nullptr;
251+
futureContext->indirectResult = futureFragment->getStoragePtr();
269252
}
270253

271254
// Configure the initial context.

unittests/runtime/TaskFuture.cpp

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,11 @@ using InvokeFunctionRef =
3030
using BodyFunctionRef =
3131
llvm::function_ref<void(AsyncTask *task)>;
3232

33-
template <class Storage> struct PODFutureContext : AsyncContext {
34-
alignas(Storage) char resultStorage[sizeof(Storage)];
35-
void *errorStorage;
36-
};
37-
38-
template <class Storage> struct FutureContext : PODFutureContext<Storage> {
33+
template <class Storage> struct FutureContext : FutureAsyncContext {
3934
InvokeFunctionRef<Storage> storedInvokeFn;
4035

4136
Storage& getStorage() {
42-
return *reinterpret_cast<Storage *>(&this->resultStorage[0]);
37+
return *reinterpret_cast<Storage *>(this->indirectResult);
4338
}
4439
};
4540

@@ -72,9 +67,7 @@ static void withFutureTask(const Metadata *resultType,
7267
auto taskAndContext =
7368
swift_task_create_future_f(flags, /*parent*/ nullptr, resultType,
7469
&futureTaskInvokeFunction<T>,
75-
sizeof(FutureContext<T>),
76-
offsetof(PODFutureContext<T>, resultStorage),
77-
offsetof(PODFutureContext<T>, errorStorage));
70+
sizeof(FutureContext<T>));
7871

7972
auto futureContext =
8073
static_cast<FutureContext<T>*>(taskAndContext.InitialContext);
@@ -139,7 +132,7 @@ TEST(TaskFutureTest, intFuture) {
139132
EXPECT_EQ(42, context->getStorage());
140133

141134
// The error storage should have been cleared out for us.
142-
EXPECT_EQ(nullptr, context->errorStorage);
135+
EXPECT_EQ(nullptr, context->errorResult);
143136

144137
// Store something in the future.
145138
context->getStorage() = 17;
@@ -173,7 +166,7 @@ TEST(TaskFutureTest, objectFuture) {
173166
object = allocTestObject(&objectValueOnComplete, 25);
174167

175168
// The error storage should have been cleared out for us.
176-
EXPECT_EQ(nullptr, context->errorStorage);
169+
EXPECT_EQ(nullptr, context->errorResult);
177170

178171
// Store the object in the future.
179172
context->getStorage() = object;
@@ -202,5 +195,4 @@ TEST(TaskFutureTest, objectFuture) {
202195
swift_release(task);
203196
assert(objectValueOnComplete == 25);
204197
});
205-
206198
}

0 commit comments

Comments
 (0)