Skip to content

Commit c291eb5

Browse files
committed
[Concurrency] Add cancelAsyncTask() builtin.
Implement a new builtin, `cancelAsyncTask()`, to cancel the given asynchronous task. This lowers down to a call into the runtime operation `swift_task_cancel()`. Use this builtin to implement Task.Handle.cancel().
1 parent 81f5528 commit c291eb5

File tree

16 files changed

+88
-7
lines changed

16 files changed

+88
-7
lines changed

include/swift/ABI/Task.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class AsyncTask : public HeapObject, public Job {
182182
/// prevent it from being corrupted in flight.
183183
AsyncContext * __ptrauth_swift_task_resume_context ResumeContext;
184184

185-
/// The currntly-active information about cancellation.
185+
/// The currently-active information about cancellation.
186186
std::atomic<ActiveTaskStatus> Status;
187187

188188
/// Reserved for the use of the task-local stack allocator.

include/swift/AST/Builtins.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,12 @@ BUILTIN_MISC_OPERATION(IntInstrprofIncrement, "int_instrprof_increment", "", Spe
723723
// function is executing.
724724
BUILTIN_MISC_OPERATION_WITH_SILGEN(GetCurrentAsyncTask, "getCurrentAsyncTask", "n", Special)
725725

726+
/// cancelAsyncTask(): (Builtin.NativeObject) -> Void
727+
///
728+
/// Cancel the given asynchronous task.
729+
BUILTIN_MISC_OPERATION_WITH_SILGEN(CancelAsyncTask, "cancelAsyncTask", "", Special)
730+
731+
726732
/// globalStringTablePointer has type String -> Builtin.RawPointer.
727733
/// It returns an immortal, global string table pointer for strings constructed
728734
/// from string literals. We consider it effects as readnone meaning that it

include/swift/Runtime/RuntimeFunctions.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,14 @@ FUNCTION(TaskDealloc,
14891489
ARGS(SwiftTaskPtrTy, Int8PtrTy),
14901490
ATTRS(NoUnwind, ArgMemOnly))
14911491

1492+
// void swift_task_dealloc(AsyncTask *task, void *ptr);
1493+
FUNCTION(TaskCancel,
1494+
swift_task_cancel, SwiftCC,
1495+
ConcurrencyAvailability,
1496+
RETURNS(VoidTy),
1497+
ARGS(SwiftTaskPtrTy),
1498+
ATTRS(NoUnwind, ArgMemOnly))
1499+
14921500
#undef RETURNS
14931501
#undef ARGS
14941502
#undef ATTRS

lib/AST/Builtins.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,11 @@ static ValueDecl *getGetCurrentAsyncTask(ASTContext &ctx, Identifier id) {
13451345
return getBuiltinFunction(id, { }, ctx.TheNativeObjectType);
13461346
}
13471347

1348+
static ValueDecl *getCancelAsyncTask(ASTContext &ctx, Identifier id) {
1349+
return getBuiltinFunction(
1350+
id, { ctx.TheNativeObjectType }, ctx.TheEmptyTupleType);
1351+
}
1352+
13481353
static ValueDecl *getPoundAssert(ASTContext &Context, Identifier Id) {
13491354
auto int1Type = BuiltinIntegerType::get(1, Context);
13501355
auto optionalRawPointerType = BoundGenericEnumType::get(
@@ -2474,6 +2479,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
24742479
case BuiltinValueKind::GetCurrentAsyncTask:
24752480
return getGetCurrentAsyncTask(Context, Id);
24762481

2482+
case BuiltinValueKind::CancelAsyncTask:
2483+
return getCancelAsyncTask(Context, Id);
2484+
24772485
case BuiltinValueKind::PoundAssert:
24782486
return getPoundAssert(Context, Id);
24792487

lib/IRGen/GenBuiltin.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
220220

221221
// Everything else cares about the (rvalue) argument.
222222

223+
if (Builtin.ID == BuiltinValueKind::CancelAsyncTask) {
224+
emitTaskCancel(IGF, args.claimNext());
225+
return;
226+
}
227+
223228
// If this is an LLVM IR intrinsic, lower it to an intrinsic call.
224229
const IntrinsicInfo &IInfo = IGF.getSILModule().getIntrinsicInfo(FnId);
225230
llvm::Intrinsic::ID IID = IInfo.ID;

lib/IRGen/GenCall.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3459,6 +3459,18 @@ void irgen::emitTaskDealloc(IRGenFunction &IGF, Address address,
34593459
llvm::Attribute::ReadNone);
34603460
}
34613461

3462+
void irgen::emitTaskCancel(IRGenFunction &IGF, llvm::Value *task) {
3463+
if (task->getType() != IGF.IGM.SwiftTaskPtrTy) {
3464+
task = IGF.Builder.CreateBitCast(task, IGF.IGM.SwiftTaskPtrTy);
3465+
}
3466+
3467+
auto *call = IGF.Builder.CreateCall(IGF.IGM.getTaskCancelFn(), {task});
3468+
call->setDoesNotThrow();
3469+
call->setCallingConv(IGF.IGM.SwiftCC);
3470+
call->addAttribute(llvm::AttributeList::FunctionIndex,
3471+
llvm::Attribute::ReadNone);
3472+
}
3473+
34623474
std::pair<Address, Size> irgen::emitAllocAsyncContext(IRGenFunction &IGF,
34633475
AsyncContextLayout layout,
34643476
llvm::Value *sizeValue,

lib/IRGen/GenCall.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ namespace irgen {
388388
Address emitTaskAlloc(IRGenFunction &IGF, llvm::Value *size,
389389
Alignment alignment);
390390
void emitTaskDealloc(IRGenFunction &IGF, Address address, llvm::Value *size);
391+
void emitTaskCancel(IRGenFunction &IGF, llvm::Value *task);
392+
391393
/// Allocate task local storage for the specified layout but using the
392394
/// provided dynamic size. Allowing the size to be specified dynamically is
393395
/// necessary for applies of thick functions the sizes of whose async contexts

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ CONSTANT_OWNERSHIP_INST(None, MustBeLive, UncheckedTakeEnumDataAddr)
231231
CONSTANT_OWNERSHIP_INST(None, MustBeLive, UnconditionalCheckedCastAddr)
232232
CONSTANT_OWNERSHIP_INST(None, MustBeLive, AllocValueBuffer)
233233
CONSTANT_OWNERSHIP_INST(None, MustBeLive, DeallocValueBuffer)
234+
234235
#define NEVER_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
235236
CONSTANT_OWNERSHIP_INST(None, MustBeLive, Load##Name)
236237
#define ALWAYS_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
@@ -1033,6 +1034,8 @@ ANY_OWNERSHIP_BUILTIN(IntInstrprofIncrement)
10331034
}
10341035
CONSTANT_OWNERSHIP_BUILTIN(Owned, MustBeInvalidated, COWBufferForReading)
10351036
CONSTANT_OWNERSHIP_BUILTIN(Owned, MustBeInvalidated, UnsafeGuaranteed)
1037+
CONSTANT_OWNERSHIP_BUILTIN(Guaranteed, MustBeLive, CancelAsyncTask)
1038+
10361039
#undef CONSTANT_OWNERSHIP_BUILTIN
10371040

10381041
#define SHOULD_NEVER_VISIT_BUILTIN(ID) \

lib/SIL/IR/ValueOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ CONSTANT_OWNERSHIP_BUILTIN(None, TypePtrAuthDiscriminator)
542542
CONSTANT_OWNERSHIP_BUILTIN(None, IntInstrprofIncrement)
543543
CONSTANT_OWNERSHIP_BUILTIN(None, GlobalStringTablePointer)
544544
CONSTANT_OWNERSHIP_BUILTIN(Owned, GetCurrentAsyncTask)
545+
CONSTANT_OWNERSHIP_BUILTIN(None, CancelAsyncTask)
545546

546547
#undef CONSTANT_OWNERSHIP_BUILTIN
547548

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,7 @@ static void visitBuiltinAddress(BuiltinInst *builtin,
17991799
case BuiltinValueKind::PoundAssert:
18001800
case BuiltinValueKind::IntInstrprofIncrement:
18011801
case BuiltinValueKind::TSanInoutAccess:
1802+
case BuiltinValueKind::CancelAsyncTask:
18021803
return;
18031804

18041805
// General memory access to a pointer in first operand position.

lib/SILGen/SILGenBuiltin.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,6 +1405,20 @@ static ManagedValue emitBuiltinGetCurrentAsyncTask(
14051405
return SGF.emitManagedRValueWithEndLifetimeCleanup(apply);
14061406
}
14071407

1408+
// Emit SIL for the named builtin: getCurrentAsyncTask.
1409+
static ManagedValue emitBuiltinCancelAsyncTask(
1410+
SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs,
1411+
ArrayRef<ManagedValue> args, SGFContext C) {
1412+
ASTContext &ctx = SGF.getASTContext();
1413+
auto argument = args[0].borrow(SGF, loc).forward(SGF);
1414+
auto apply = SGF.B.createBuiltin(
1415+
loc,
1416+
ctx.getIdentifier(getBuiltinName(BuiltinValueKind::CancelAsyncTask)),
1417+
SGF.getLoweredType(ctx.TheEmptyTupleType), SubstitutionMap(),
1418+
{ argument });
1419+
return ManagedValue::forUnmanaged(apply);
1420+
}
1421+
14081422
Optional<SpecializedEmitter>
14091423
SpecializedEmitter::forDecl(SILGenModule &SGM, SILDeclRef function) {
14101424
// Only consider standalone declarations in the Builtin module.

lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ static bool isBarrier(SILInstruction *inst) {
165165
case BuiltinValueKind::AssignTakeArray:
166166
case BuiltinValueKind::UnsafeGuaranteed:
167167
case BuiltinValueKind::UnsafeGuaranteedEnd:
168+
case BuiltinValueKind::CancelAsyncTask:
168169
return true;
169170
}
170171
}

stdlib/public/Concurrency/Task.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ extension Task {
102102
/// i.e. the task will run regardless of the handle still being present or not.
103103
/// Dropping a handle however means losing the ability to await on the task's result
104104
/// and losing the ability to cancel it.
105-
public final class Handle<Success> {
105+
@_frozen
106+
public struct Handle<Success> {
107+
private let task: Builtin.NativeObject
108+
106109
/// Wait for the task to complete, returning (or throwing) its result.
107110
///
108111
/// ### Priority
@@ -130,7 +133,7 @@ extension Task {
130133
/// their "actual work", however this is not a requirement nor is it guaranteed
131134
/// how and when tasks check for cancellation in general.
132135
public func cancel() {
133-
fatalError("\(#function) not implemented yet.")
136+
Builtin.cancelAsyncTask(task)
134137
}
135138
}
136139
}

stdlib/public/Concurrency/TaskStatus.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ JobPriority
505505
swift::swift_task_escalate(AsyncTask *task, JobPriority newPriority) {
506506
Optional<StatusRecordLockRecord> recordLockRecord;
507507

508-
// Fast path: check that the task's priority is not already at laest
508+
// Fast path: check that the task's priority is not already at least
509509
// as high as the target. The task's priority can only be modified
510510
// under the status record lock; it's possible that the priority could
511511
// be getting simultaneously escalated, but it's okay for us to return

test/IRGen/async/builtins.sil

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %target-swift-frontend -enable-experimental-concurrency -enable-objc-interop -primary-file %s -emit-ir | %FileCheck %s -DINT=i%target-ptrsize --check-prefixes=CHECK,CHECK-objc
2-
// RUN: %target-swift-frontend -enable-experimental-concurrency -disable-objc-interop -primary-file %s -emit-ir | %FileCheck %s -DINT=i%target-ptrsize --check-prefixes=CHECK,CHECK-native
1+
// RUN: %target-swift-frontend -enable-experimental-concurrency -enable-objc-interop -primary-file %s -emit-ir -sil-verify-all | %FileCheck %s -DINT=i%target-ptrsize --check-prefixes=CHECK,CHECK-objc
2+
// RUN: %target-swift-frontend -enable-experimental-concurrency -disable-objc-interop -primary-file %s -emit-ir -sil-verify-all | %FileCheck %s -DINT=i%target-ptrsize --check-prefixes=CHECK,CHECK-native
33

44
// REQUIRES: concurrency
55

@@ -17,3 +17,14 @@ bb0:
1717
end_lifetime %0 : $Builtin.NativeObject
1818
return %1 : $Builtin.NativeObject
1919
}
20+
21+
// CHECK-LABEL: define hidden swiftcc void @cancel_task(%swift.refcounted* %0)
22+
// CHECK: [[TASK:%.*]] = bitcast %swift.refcounted* %0 to %swift.task*
23+
// CHECK-NEXT: call swiftcc void @swift_task_cancel(%swift.task* [[TASK]])
24+
25+
sil hidden [ossa] @cancel_task : $@convention(method) (@guaranteed Builtin.NativeObject) -> () {
26+
bb0(%0 : @guaranteed $Builtin.NativeObject):
27+
%4 = builtin "cancelAsyncTask"(%0 : $Builtin.NativeObject) : $()
28+
%5 = tuple ()
29+
return %5 : $()
30+
}

test/SILGen/async_builtins.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 -enable-experimental-concurrency -parse-stdlib | %FileCheck %s
1+
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 -enable-experimental-concurrency -parse-stdlib -sil-verify-all | %FileCheck %s
22
// REQUIRES: concurrency
33

44
struct X {
@@ -7,4 +7,10 @@ struct X {
77
// CHECK: builtin "getCurrentAsyncTask"() : $Builtin.NativeObject
88
return Builtin.getCurrentAsyncTask()
99
}
10+
11+
// CHECK-LABEL: sil hidden [ossa] @$s4test1XV8doCancel4taskyBo_tF : $@convention(method) (@guaranteed Builtin.NativeObject, X) -> ()
12+
func doCancel(task: Builtin.NativeObject) {
13+
// CHECK: builtin "cancelAsyncTask"(%0 : $Builtin.NativeObject) : $()
14+
Builtin.cancelAsyncTask(task)
15+
}
1016
}

0 commit comments

Comments
 (0)