Skip to content

Progress towards storing task/executor state in thread-local storage #36079

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

Merged
merged 4 commits into from
Feb 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions include/swift/ABI/Task.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,17 @@ class alignas(2 * alignof(void*)) Job {
return Flags.getPriority();
}

/// Run this job.
void run(ExecutorRef currentExecutor);
/// Given that we've fully established the job context in the current
/// thread, actually start running this job. To establish the context
/// correctly, call swift_job_run or runJobInExecutorContext.
void runInFullyEstablishedContext(ExecutorRef currentExecutor);

/// Given that we've fully established the job context in the
/// current thread, and that the job is a simple (non-task) job,
/// actually start running this job.
void runSimpleInFullyEstablishedContext(ExecutorRef currentExecutor) {
RunJob(this, currentExecutor);
}
};

// The compiler will eventually assume these.
Expand Down Expand Up @@ -173,7 +182,11 @@ class AsyncTask : public HeapObject, public Job {
assert(flags.isAsyncTask());
}

void run(ExecutorRef currentExecutor) {
/// Given that we've already fully established the job context
/// in the current thread, start running this task. To establish
/// the job context correctly, call swift_job_run or
/// runInExecutorContext.
void runInFullyEstablishedContext(ExecutorRef currentExecutor) {
ResumeTask(this, currentExecutor, ResumeContext);
}

Expand Down Expand Up @@ -860,6 +873,10 @@ class AsyncTask : public HeapObject, public Job {
/// Destroy the storage associated with the future.
void destroy();

const Metadata *getResultType() const {
return resultType;
}

/// Retrieve a pointer to the storage of result.
OpaqueValue *getStoragePtr() {
return reinterpret_cast<OpaqueValue *>(
Expand Down Expand Up @@ -941,11 +958,11 @@ static_assert(sizeof(AsyncTask) == 12 * sizeof(void*),
static_assert(alignof(AsyncTask) == 2 * alignof(void*),
"AsyncTask alignment is wrong");

inline void Job::run(ExecutorRef currentExecutor) {
inline void Job::runInFullyEstablishedContext(ExecutorRef currentExecutor) {
if (auto task = dyn_cast<AsyncTask>(this))
task->run(currentExecutor);
task->runInFullyEstablishedContext(currentExecutor);
else
RunJob(this, currentExecutor);
runSimpleInFullyEstablishedContext(currentExecutor);
}

/// An asynchronous context within a task. Generally contexts are
Expand Down
49 changes: 29 additions & 20 deletions include/swift/Runtime/Concurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,37 +117,36 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
JobPriority
swift_task_escalate(AsyncTask *task, JobPriority newPriority);

/// The result of waiting for a task future.
struct TaskFutureWaitResult {
/// Whether the storage represents the error result vs. the successful
/// result.
bool hadErrorResult;

/// Storage for the result of the future.
///
/// When the future completed normally, this is a pointer to the storage
/// of the result value, which lives inside the future task itself.
///
/// When the future completed by throwing an error, this is the error
/// object itself.
OpaqueValue *storage;
};

using TaskFutureWaitSignature =
AsyncSignature<TaskFutureWaitResult(AsyncTask *), /*throws*/ false>;
AsyncSignature<void(AsyncTask *, OpaqueValue *), /*throws*/ false>;

/// Wait for a future task to complete.
/// Wait for a non-throwing future task to complete.
///
/// This can be called from any thread. Its Swift signature is
///
/// \code
/// func swift_task_future_wait(on task: Builtin.NativeObject) async
/// -> TaskFutureWaitResult
/// func swift_task_future_wait(on task: _owned Builtin.NativeObject) async
/// -> Success
/// \endcode
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync)
TaskFutureWaitSignature::FunctionType
swift_task_future_wait;

using TaskFutureWaitThrowingSignature =
AsyncSignature<void(AsyncTask *, OpaqueValue *), /*throws*/ true>;

/// Wait for a potentially-throwing future task to complete.
///
/// This can be called from any thread. Its Swift signature is
///
/// \code
/// func swift_task_future_wait_throwing(on task: _owned Builtin.NativeObject)
/// async throws -> Success
/// \endcode
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync)
TaskFutureWaitThrowingSignature::FunctionType
swift_task_future_wait_throwing;

/// Wait for a readyQueue of a Channel to become non empty.
///
/// This can be called from any thread. Its Swift signature is
Expand Down Expand Up @@ -422,6 +421,16 @@ void swift_continuation_logFailedCheck(const char *message);
/// Otherwise it uses dispatchMain.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_task_asyncMainDrainQueue();

/// Establish that the current thread is running as the given
/// executor, then run a job.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_job_run(Job *job, ExecutorRef executor);

/// Return the current thread's active task reference.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
AsyncTask *swift_task_getCurrent(void);

}

#endif
95 changes: 95 additions & 0 deletions include/swift/Runtime/Error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//===--- Error.h - Swift Runtime ABI for error values -----------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Swift runtime support for working with error values.
//
// The ABI here is quite different in ObjC and non-ObjC modes.
// In ObjC mode, SwiftError is closely related to the NSError class:
// native errors are boxed as a subclass of NSError, but non-native
// errors may simply be NSError objects directly from Objective-C.
// In non-ObjC mode, SwiftError boxes are always native.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_RUNTIME_ERROR_H
#define SWIFT_RUNTIME_ERROR_H

#include "swift/Runtime/HeapObject.h"
#include "swift/Runtime/Metadata.h"

namespace swift {

struct SwiftError;

/// Allocate a catchable error object.
///
/// If value is nonnull, it should point to a value of \c type, which will be
/// copied (or taken if \c isTake is true) into the newly-allocated error box.
/// If value is null, the box's contents will be left uninitialized, and
/// \c isTake should be false.
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
BoxPair swift_allocError(const Metadata *type,
const WitnessTable *errorConformance,
OpaqueValue *value, bool isTake);

/// Deallocate an error object whose contained object has already been
/// destroyed.
SWIFT_RUNTIME_STDLIB_API
void swift_deallocError(SwiftError *error, const Metadata *type);

struct ErrorValueResult {
const OpaqueValue *value;
const Metadata *type;
const WitnessTable *errorConformance;
};

/// Extract a pointer to the value, the type metadata, and the Error
/// protocol witness from an error object.
///
/// The "scratch" pointer should point to an uninitialized word-sized
/// temporary buffer. The implementation may write a reference to itself to
/// that buffer if the error object is a toll-free-bridged NSError instead of
/// a native Swift error, in which case the object itself is the "boxed" value.
SWIFT_RUNTIME_STDLIB_API
void swift_getErrorValue(const SwiftError *errorObject,
void **scratch,
ErrorValueResult *out);

/// Called when throwing an error. Serves as a breakpoint hook
/// for debuggers.
SWIFT_CC(swift)
SWIFT_RUNTIME_STDLIB_API void
swift_willThrow(SWIFT_CONTEXT void *unused,
SWIFT_ERROR_RESULT SwiftError **object);

/// Called when an error is thrown out of the top level of a script.
SWIFT_CC(swift)
SWIFT_RUNTIME_STDLIB_API SWIFT_NORETURN void
swift_errorInMain(SwiftError *object);

/// Called when the try! operator fails.
SWIFT_CC(swift)
SWIFT_RUNTIME_STDLIB_API SWIFT_NORETURN void
swift_unexpectedError(SwiftError *object, OpaqueValue *filenameStart,
long filenameLength, bool isAscii, unsigned long line);

/// Retain an error box.
SWIFT_RUNTIME_STDLIB_API
SwiftError *swift_errorRetain(SwiftError *object);

/// Release an error box.
SWIFT_RUNTIME_STDLIB_API
void swift_errorRelease(SwiftError *object);

} // end namespace swift

#endif
110 changes: 81 additions & 29 deletions lib/IRGen/Callee.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,28 +125,76 @@ namespace irgen {
/// A function pointer value.
class FunctionPointer {
public:
struct KindTy {
enum class Value {
Function,
AsyncFunctionPointer,
};
static const Value Function = Value::Function;
static const Value AsyncFunctionPointer = Value::AsyncFunctionPointer;
Value value;
KindTy(Value value) : value(value) {}
KindTy(CanSILFunctionType fnType)
: value(fnType->isAsync() ? Value::AsyncFunctionPointer
: Value::Function) {}
friend bool operator==(const KindTy &lhs, const KindTy &rhs) {
enum class BasicKind {
Function,
AsyncFunctionPointer
};

enum class SpecialKind {
TaskFutureWait,
TaskFutureWaitThrowing,
TaskGroupWaitNext,
};

class Kind {
static constexpr unsigned SpecialOffset = 2;
unsigned value;
public:
static constexpr BasicKind Function =
BasicKind::Function;
static constexpr BasicKind AsyncFunctionPointer =
BasicKind::AsyncFunctionPointer;

Kind(BasicKind kind) : value(unsigned(kind)) {}
Kind(SpecialKind kind) : value(unsigned(kind) + SpecialOffset) {}
Kind(CanSILFunctionType fnType)
: Kind(fnType->isAsync() ? BasicKind::AsyncFunctionPointer
: BasicKind::Function) {}

BasicKind getBasicKind() const {
return value < SpecialOffset ? BasicKind(value) : BasicKind::Function;
}
bool isAsyncFunctionPointer() const {
return value == unsigned(BasicKind::AsyncFunctionPointer);
}

bool isSpecial() const {
return value >= SpecialOffset;
}
SpecialKind getSpecialKind() const {
assert(isSpecial());
return SpecialKind(value - SpecialOffset);
}

/// Should we suppress the generic signature from the given function?
///
/// This is a micro-optimization we apply to certain special functions
/// that we know don't need generics.
bool suppressGenerics() const {
if (!isSpecial()) return false;

switch (getSpecialKind()) {
case SpecialKind::TaskFutureWait:
case SpecialKind::TaskFutureWaitThrowing:
// We suppress generics from these as a code-size optimization
// because the runtime can recover the success type from the
// future.
return true;
case SpecialKind::TaskGroupWaitNext:
return false;
}
}

friend bool operator==(Kind lhs, Kind rhs) {
return lhs.value == rhs.value;
}
friend bool operator!=(const KindTy &lhs, const KindTy &rhs) {
friend bool operator!=(Kind lhs, Kind rhs) {
return !(lhs == rhs);
}
};

private:
KindTy Kind;
Kind kind;

/// The actual pointer, either to the function or to its descriptor.
llvm::Value *Value;
Expand All @@ -155,34 +203,29 @@ namespace irgen {

Signature Sig;

bool isFunctionPointerWithoutContext = false;

public:
/// Construct a FunctionPointer for an arbitrary pointer value.
/// We may add more arguments to this; try to use the other
/// constructors/factories if possible.
explicit FunctionPointer(KindTy kind, llvm::Value *value,
explicit FunctionPointer(Kind kind, llvm::Value *value,
PointerAuthInfo authInfo,
const Signature &signature,
bool isWithoutCtxt = false)
: Kind(kind), Value(value), AuthInfo(authInfo), Sig(signature),
isFunctionPointerWithoutContext(isWithoutCtxt) {
const Signature &signature)
: kind(kind), Value(value), AuthInfo(authInfo), Sig(signature) {
// The function pointer should have function type.
assert(value->getType()->getPointerElementType()->isFunctionTy());
// TODO: maybe assert similarity to signature.getType()?
}

// Temporary only!
explicit FunctionPointer(KindTy kind, llvm::Value *value,
const Signature &signature,
bool isWithoutCtxt = false)
: FunctionPointer(kind, value, PointerAuthInfo(), signature, isWithoutCtxt) {}
explicit FunctionPointer(Kind kind, llvm::Value *value,
const Signature &signature)
: FunctionPointer(kind, value, PointerAuthInfo(), signature) {}

static FunctionPointer forDirect(IRGenModule &IGM,
llvm::Constant *value,
CanSILFunctionType fnType);

static FunctionPointer forDirect(KindTy kind, llvm::Constant *value,
static FunctionPointer forDirect(Kind kind, llvm::Constant *value,
const Signature &signature) {
return FunctionPointer(kind, value, PointerAuthInfo(), signature);
}
Expand All @@ -197,7 +240,8 @@ namespace irgen {
return (isa<llvm::Constant>(Value) && AuthInfo.isConstant());
}

KindTy getKind() const { return Kind; }
Kind getKind() const { return kind; }
BasicKind getBasicKind() const { return kind.getBasicKind(); }

/// Given that this value is known to have been constructed from a direct
/// function, Return the name of that function.
Expand Down Expand Up @@ -250,7 +294,11 @@ namespace irgen {
FunctionPointer getAsFunction(IRGenFunction &IGF) const;

bool useStaticContextSize() const {
return isFunctionPointerWithoutContext;
return !kind.isAsyncFunctionPointer();
}

bool suppressGenerics() const {
return kind.suppressGenerics();
}
};

Expand Down Expand Up @@ -315,6 +363,10 @@ namespace irgen {
return Fn.getSignature();
}

bool suppressGenerics() const {
return Fn.suppressGenerics();
}

/// If this callee has a value for the Swift context slot, return
/// it; otherwise return non-null.
llvm::Value *getSwiftContext() const;
Expand Down
Loading