Skip to content

Commit bd62fdb

Browse files
committed
[Concurrency] Make Job/AsyncTask minimally compatible with dispatch object layout
Create a TargetDispatchClassMetadata for Swift metadata that also has a dispatch-compatible vtable. Dispatch leaves room for ObjC class metadata so the two regions don't overlap. (The vtable currently consists of a single dummy entry; this will be filled out later.) Rearrange the Job and AsyncTask hierarchy so that AsyncTask inherits only from Job, which in turn inherits from HeapObject. This gives all Job instances a dispatch-compatible isa field. It also gives them a refcount word, which is wasted on instances that aren't AsyncTask instances. Maybe we can find some use for that space in the future. rdar://75227953
1 parent bdacb68 commit bd62fdb

File tree

8 files changed

+71
-22
lines changed

8 files changed

+71
-22
lines changed

include/swift/ABI/Metadata.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,26 @@ struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
13331333
};
13341334
using ClassMetadata = TargetClassMetadata<InProcess>;
13351335

1336+
/// The structure of class metadata that's compatible with dispatch objects.
1337+
/// This includes Swift heap metadata, followed by the vtable entries that
1338+
/// dispatch expects to see, with padding to place them at the expected offsets.
1339+
template <typename Runtime>
1340+
struct TargetDispatchClassMetadata : public TargetHeapMetadata<Runtime> {
1341+
using DummyVTableCall = void (*)(void);
1342+
1343+
TargetDispatchClassMetadata(MetadataKind Kind,
1344+
DummyVTableCall DummyVTableEntry)
1345+
: TargetHeapMetadata<Runtime>(Kind), DummyVTableEntry(DummyVTableEntry) {}
1346+
1347+
TargetPointer<Runtime, void> Opaque;
1348+
#if SWIFT_OBJC_INTEROP
1349+
TargetPointer<Runtime, void> OpaqueObjC[3];
1350+
#endif
1351+
1352+
TargetSignedPointer<Runtime, DummyVTableCall> DummyVTableEntry;
1353+
};
1354+
using DispatchClassMetadata = TargetDispatchClassMetadata<InProcess>;
1355+
13361356
/// The structure of metadata for heap-allocated local variables.
13371357
/// This is non-type metadata.
13381358
template <typename Runtime>

include/swift/ABI/MetadataKind.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ METADATAKIND(ErrorObject,
8989
METADATAKIND(Task,
9090
2 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)
9191

92+
/// A non-task async job.
93+
METADATAKIND(Job,
94+
3 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)
95+
9296
// getEnumeratedMetadataKind assumes that all the enumerated values here
9397
// will be <= LastEnumeratedMetadataKind.
9498

include/swift/ABI/Task.h

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ struct SwiftError;
3636
class TaskStatusRecord;
3737
class TaskGroup;
3838

39+
extern FullMetadata<DispatchClassMetadata> jobHeapMetadata;
40+
3941
/// A schedulable job.
40-
class alignas(2 * alignof(void*)) Job {
42+
class alignas(2 * alignof(void*)) Job : public HeapObject {
4143
protected:
4244
// Indices into SchedulerPrivate, for use by the runtime.
4345
enum {
@@ -62,13 +64,15 @@ class alignas(2 * alignof(void*)) Job {
6264
TaskContinuationFunction * __ptrauth_swift_task_resume_function ResumeTask;
6365
};
6466

65-
Job(JobFlags flags, JobInvokeFunction *invoke)
66-
: Flags(flags), RunJob(invoke) {
67+
Job(JobFlags flags, JobInvokeFunction *invoke,
68+
const HeapMetadata *metadata = &jobHeapMetadata)
69+
: HeapObject(metadata), Flags(flags), RunJob(invoke) {
6770
assert(!isAsyncTask() && "wrong constructor for a task");
6871
}
6972

70-
Job(JobFlags flags, TaskContinuationFunction *invoke)
71-
: Flags(flags), ResumeTask(invoke) {
73+
Job(JobFlags flags, TaskContinuationFunction *invoke,
74+
const HeapMetadata *metadata = &jobHeapMetadata)
75+
: HeapObject(metadata), Flags(flags), ResumeTask(invoke) {
7276
assert(isAsyncTask() && "wrong constructor for a non-task job");
7377
}
7478

@@ -94,7 +98,7 @@ class alignas(2 * alignof(void*)) Job {
9498
};
9599

96100
// The compiler will eventually assume these.
97-
static_assert(sizeof(Job) == 4 * sizeof(void*),
101+
static_assert(sizeof(Job) == 6 * sizeof(void*),
98102
"Job size is wrong");
99103
static_assert(alignof(Job) == 2 * alignof(void*),
100104
"Job alignment is wrong");
@@ -154,7 +158,7 @@ class ActiveTaskStatus {
154158
///
155159
/// * The future fragment is dynamic in size, based on the future result type
156160
/// it can hold, and thus must be the *last* fragment.
157-
class AsyncTask : public HeapObject, public Job {
161+
class AsyncTask : public Job {
158162
public:
159163
/// The context for resuming the job. When a task is scheduled
160164
/// as a job, the next continuation should be installed as the
@@ -178,7 +182,7 @@ class AsyncTask : public HeapObject, public Job {
178182
AsyncTask(const HeapMetadata *metadata, JobFlags flags,
179183
TaskContinuationFunction *run,
180184
AsyncContext *initialContext)
181-
: HeapObject(metadata), Job(flags, run),
185+
: Job(flags, run, metadata),
182186
ResumeContext(initialContext),
183187
Status(ActiveTaskStatus()),
184188
Local(TaskLocal::Storage()) {
@@ -444,7 +448,6 @@ class AsyncTask : public HeapObject, public Job {
444448
return reinterpret_cast<AsyncTask *&>(
445449
SchedulerPrivate[NextWaitingTaskIndex]);
446450
}
447-
448451
};
449452

450453
// The compiler will eventually assume these.

lib/IRGen/GenBuiltin.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,8 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
265265

266266
if (Builtin.ID == BuiltinValueKind::ConvertTaskToJob) {
267267
auto task = args.claimNext();
268-
// The job object starts immediately past the heap-object header.
269-
auto bytes = IGF.Builder.CreateBitCast(task, IGF.IGM.Int8PtrTy);
270-
auto offset = IGF.IGM.RefCountedStructSize;
271-
bytes = IGF.Builder.CreateInBoundsGEP(bytes, IGF.IGM.getSize(offset));
272-
auto job = IGF.Builder.CreateBitCast(bytes, IGF.IGM.SwiftJobPtrTy);
268+
// The job object starts at the beginning of the task.
269+
auto job = IGF.Builder.CreateBitCast(task, IGF.IGM.SwiftJobPtrTy);
273270
out.add(job);
274271
return;
275272
}

lib/IRGen/IRGenModule.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,8 +619,10 @@ IRGenModule::IRGenModule(IRGenerator &irgen,
619619
SwiftTaskGroupPtrTy = Int8PtrTy; // we pass it opaquely (TaskGroup*)
620620
SwiftExecutorPtrTy = SwiftExecutorTy->getPointerTo(DefaultAS);
621621
SwiftJobTy = createStructType(*this, "swift.job", {
622+
RefCountedStructTy, // object header
623+
Int8PtrTy, Int8PtrTy, // SchedulerPrivate
622624
SizeTy, // flags
623-
Int8PtrTy // execution function pointer
625+
FunctionPtrTy, // RunJob/ResumeTask
624626
});
625627
SwiftJobPtrTy = SwiftJobTy->getPointerTo();
626628

stdlib/public/Concurrency/GlobalExecutor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ static DelayedJob *DelayedJobQueue = nullptr;
8686

8787
/// Get the next-in-queue storage slot.
8888
static Job *&nextInQueue(Job *cur) {
89-
return reinterpret_cast<Job*&>(cur->SchedulerPrivate);
89+
return reinterpret_cast<Job*&>(&cur->SchedulerPrivate[NextWaitingTaskIndex]);
9090
}
9191

9292
/// Insert a job into the cooperative global queue.

stdlib/public/Concurrency/Task.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ void AsyncTask::completeFuture(AsyncContext *context, ExecutorRef executor) {
144144
}
145145
}
146146

147+
SWIFT_CC(swift)
148+
static void destroyJob(SWIFT_CONTEXT HeapObject *obj) {
149+
assert(false && "A non-task job should never be destroyed as heap metadata.");
150+
}
151+
147152
SWIFT_CC(swift)
148153
static void destroyTask(SWIFT_CONTEXT HeapObject *obj) {
149154
auto task = static_cast<AsyncTask*>(obj);
@@ -165,8 +170,27 @@ static void destroyTask(SWIFT_CONTEXT HeapObject *obj) {
165170
free(task);
166171
}
167172

173+
static void dummyVTableFunction(void) {
174+
abort();
175+
}
176+
177+
FullMetadata<DispatchClassMetadata> swift::jobHeapMetadata = {
178+
{
179+
{
180+
&destroyJob
181+
},
182+
{
183+
/*value witness table*/ nullptr
184+
}
185+
},
186+
{
187+
MetadataKind::Job,
188+
dummyVTableFunction
189+
}
190+
};
191+
168192
/// Heap metadata for an asynchronous task.
169-
static FullMetadata<HeapMetadata> taskHeapMetadata = {
193+
static FullMetadata<DispatchClassMetadata> taskHeapMetadata = {
170194
{
171195
{
172196
&destroyTask
@@ -176,7 +200,8 @@ static FullMetadata<HeapMetadata> taskHeapMetadata = {
176200
}
177201
},
178202
{
179-
MetadataKind::Task
203+
MetadataKind::Task,
204+
dummyVTableFunction
180205
}
181206
};
182207

test/IRGen/builtins.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -853,10 +853,8 @@ func globalStringTablePointerUse(_ str: String) -> Builtin.RawPointer {
853853

854854
// CHECK-LABEL: define {{.*}}convertTaskToJob
855855
// CHECK: call %swift.refcounted* @swift_retain(%swift.refcounted* returned %0)
856-
// CHECK-NEXT: [[T0:%.*]] = bitcast %swift.refcounted* %0 to i8*
857-
// CHECK-NEXT: [[T1:%.*]] = getelementptr inbounds i8, i8* [[T0]], i64 16
858-
// CHECK-NEXT: [[T2:%.*]] = bitcast i8* [[T1]] to %swift.job*
859-
// CHECK-NEXT: ret %swift.job* [[T2]]
856+
// CHECK-NEXT: [[T0:%.*]] = bitcast %swift.refcounted* %0 to %swift.job*
857+
// CHECK-NEXT: ret %swift.job* [[T0]]
860858
func convertTaskToJob(_ task: Builtin.NativeObject) -> Builtin.Job {
861859
return Builtin.convertTaskToJob(task)
862860
}

0 commit comments

Comments
 (0)