diff --git a/include/swift/ABI/Task.h b/include/swift/ABI/Task.h index a81ffdfdfa764..cdc619267631c 100644 --- a/include/swift/ABI/Task.h +++ b/include/swift/ABI/Task.h @@ -301,7 +301,7 @@ class AsyncTask : public Job { #if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES static constexpr size_t ActiveTaskStatusSize = 4 * sizeof(void *); #else - static constexpr size_t ActiveTaskStatusSize = 4 * sizeof(void *); + static constexpr size_t ActiveTaskStatusSize = 2 * sizeof(void *); #endif // Private storage is currently 6 pointers, 16 bytes of non-pointer data, @@ -309,7 +309,7 @@ class AsyncTask : public Job { static constexpr size_t PrivateStorageSize = 6 * sizeof(void *) + 16 + ActiveTaskStatusSize + sizeof(RecursiveMutex); - void *Storage[PrivateStorageSize]; + char Storage[PrivateStorageSize]; /// Initialize this storage during the creation of a task. void initialize(JobPriority basePri); diff --git a/include/swift/RemoteInspection/ReflectionContext.h b/include/swift/RemoteInspection/ReflectionContext.h index 98bfe4ea4e67a..7fb183a95702c 100644 --- a/include/swift/RemoteInspection/ReflectionContext.h +++ b/include/swift/RemoteInspection/ReflectionContext.h @@ -134,11 +134,13 @@ class ReflectionContext std::vector> dataRanges; bool setupTargetPointers = false; + typename super::StoredPointer target_asyncTaskMetadata = 0; typename super::StoredPointer target_non_future_adapter = 0; typename super::StoredPointer target_future_adapter = 0; typename super::StoredPointer target_task_wait_throwing_resume_adapter = 0; typename super::StoredPointer target_task_future_wait_resume_adapter = 0; bool supportsPriorityEscalation = false; + typename super::StoredSize asyncTaskSize = 0; public: using super::getBuilder; @@ -1816,16 +1818,32 @@ class ReflectionContext ChildTask = RecordObj->FirstChild; } - while (ChildTask) { + while (ChildTask && ChildTaskLoopCount++ < ChildTaskLimit) { + // Read the child task. + auto ChildTaskObj = readObj(ChildTask); + if (!ChildTaskObj) + return {std::string("found unreadable child task pointer"), Info}; + Info.ChildTasks.push_back(ChildTask); - StoredPointer ChildFragmentAddr = ChildTask + sizeof(*AsyncTaskObj); - auto ChildFragmentObj = - readObj>(ChildFragmentAddr); - if (ChildFragmentObj) - ChildTask = ChildFragmentObj->NextChild; - else + swift::JobFlags ChildJobFlags(AsyncTaskObj->Flags); + if (ChildJobFlags.task_isChildTask()) { + if (asyncTaskSize == 0) + return {std::string("target async task size unknown, unable to " + "iterate child tasks"), + Info}; + + StoredPointer ChildFragmentAddr = ChildTask + asyncTaskSize; + auto ChildFragmentObj = + readObj>(ChildFragmentAddr); + if (ChildFragmentObj) + ChildTask = ChildFragmentObj->NextChild; + else + ChildTask = 0; + } else { + // No child fragment, so we're done iterating. ChildTask = 0; + } } RecordPtr = RecordObj->Parent; @@ -1927,7 +1945,7 @@ class ReflectionContext if (setupTargetPointers) return; - auto getFunc = [&](const std::string &name) -> StoredPointer { + auto getPointer = [&](const std::string &name) -> StoredPointer { auto Symbol = getReader().getSymbolAddress(name); if (!Symbol) return 0; @@ -1936,19 +1954,27 @@ class ReflectionContext return 0; return Pointer->getResolvedAddress().getAddressData(); }; + target_asyncTaskMetadata = + getPointer("_swift_concurrency_debug_asyncTaskMetadata"); target_non_future_adapter = - getFunc("_swift_concurrency_debug_non_future_adapter"); - target_future_adapter = getFunc("_swift_concurrency_debug_future_adapter"); - target_task_wait_throwing_resume_adapter = - getFunc("_swift_concurrency_debug_task_wait_throwing_resume_adapter"); + getPointer("_swift_concurrency_debug_non_future_adapter"); + target_future_adapter = + getPointer("_swift_concurrency_debug_future_adapter"); + target_task_wait_throwing_resume_adapter = getPointer( + "_swift_concurrency_debug_task_wait_throwing_resume_adapter"); target_task_future_wait_resume_adapter = - getFunc("_swift_concurrency_debug_task_future_wait_resume_adapter"); + getPointer("_swift_concurrency_debug_task_future_wait_resume_adapter"); auto supportsPriorityEscalationAddr = getReader().getSymbolAddress( "_swift_concurrency_debug_supportsPriorityEscalation"); if (supportsPriorityEscalationAddr) { getReader().readInteger(supportsPriorityEscalationAddr, &supportsPriorityEscalation); } + auto asyncTaskSizeAddr = + getReader().getSymbolAddress("_swift_concurrency_debug_asyncTaskSize"); + if (asyncTaskSizeAddr) { + getReader().readInteger(asyncTaskSizeAddr, &asyncTaskSize); + } setupTargetPointers = true; } diff --git a/include/swift/RemoteInspection/RuntimeInternals.h b/include/swift/RemoteInspection/RuntimeInternals.h index 52f22df294978..6a0eabae00e1c 100644 --- a/include/swift/RemoteInspection/RuntimeInternals.h +++ b/include/swift/RemoteInspection/RuntimeInternals.h @@ -64,7 +64,7 @@ template struct ConformanceCacheEntry { template struct HeapObject { - typename Runtime::StoredPointer Metadata; + typename Runtime::StoredSignedPointer Metadata; typename Runtime::StoredSize RefCounts; }; @@ -131,10 +131,7 @@ struct AsyncTask: Job { // On 64-bit, there's a Reserved64 after ResumeContext. typename Runtime::StoredPointer ResumeContextAndReserved[ sizeof(typename Runtime::StoredPointer) == 8 ? 2 : 1]; - union { - AsyncTaskPrivateStorage PrivateStorage; - typename Runtime::StoredPointer PrivateStorageRaw[14]; - }; + AsyncTaskPrivateStorage PrivateStorage; }; template diff --git a/stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift b/stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift index f38adc5eb8ee7..3b9290eac0959 100644 --- a/stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift +++ b/stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift @@ -209,6 +209,7 @@ public enum InstanceKind: UInt8 { case Enum case EnumValue case AsyncTask + case LogString } /// Represents a section in a loaded image in this process. @@ -642,6 +643,15 @@ public func reflect(asyncTask: UInt) { reflect(instanceAddress: asyncTask, kind: .AsyncTask) } +/// Log a string to the test's output. Use instead of print, which gets +/// captured by the parent and read as commands. +public func reflectionLog(str: String) { + str.withCString { + let addr = UInt(bitPattern: $0) + reflect(instanceAddress: addr, kind: .LogString); + } +} + /// Call this function to indicate to the parent that there are /// no more instances to look at. public func doneReflecting() { diff --git a/stdlib/public/Concurrency/Debug.h b/stdlib/public/Concurrency/Debug.h index 858f0b903caa2..17df69957beb1 100644 --- a/stdlib/public/Concurrency/Debug.h +++ b/stdlib/public/Concurrency/Debug.h @@ -34,6 +34,10 @@ const void *const _swift_concurrency_debug_jobMetadata; SWIFT_EXPORT_FROM(swift_Concurrency) const void *const _swift_concurrency_debug_asyncTaskMetadata; +/// The size of an AsyncTask, in bytes. +SWIFT_EXPORT_FROM(swift_Concurrency) +const size_t _swift_concurrency_debug_asyncTaskSize; + /// A fake metadata pointer placed at the start of async task slab allocations. SWIFT_EXPORT_FROM(swift_Concurrency) const void *const _swift_concurrency_debug_asyncTaskSlabMetadata; diff --git a/stdlib/public/Concurrency/Task.cpp b/stdlib/public/Concurrency/Task.cpp index 6a2cb13101cde..0e85d62cc6f28 100644 --- a/stdlib/public/Concurrency/Task.cpp +++ b/stdlib/public/Concurrency/Task.cpp @@ -440,6 +440,8 @@ const void *const swift::_swift_concurrency_debug_jobMetadata = const void *const swift::_swift_concurrency_debug_asyncTaskMetadata = static_cast(&taskHeapMetadata); +const size_t swift::_swift_concurrency_debug_asyncTaskSize = sizeof(AsyncTask); + const HeapMetadata *swift::jobHeapMetadataPtr = static_cast(&jobHeapMetadata); const HeapMetadata *swift::taskHeapMetadataPtr = diff --git a/stdlib/tools/swift-reflection-test/messages.h b/stdlib/tools/swift-reflection-test/messages.h index 7e91f8e2945f8..2353b59d2f6f2 100644 --- a/stdlib/tools/swift-reflection-test/messages.h +++ b/stdlib/tools/swift-reflection-test/messages.h @@ -29,5 +29,6 @@ typedef enum InstanceKind { Closure, Enum, EnumValue, - AsyncTask + AsyncTask, + LogString, } InstanceKind; diff --git a/stdlib/tools/swift-reflection-test/swift-reflection-test.c b/stdlib/tools/swift-reflection-test/swift-reflection-test.c index ce749e1b83c1e..d03f1b952af0f 100644 --- a/stdlib/tools/swift-reflection-test/swift-reflection-test.c +++ b/stdlib/tools/swift-reflection-test/swift-reflection-test.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +85,20 @@ static void errnoAndExit(const char *message) { #define DEBUG_LOG(fmt, ...) (void)0 #endif +#ifdef __clang__ +__attribute((__format__(__printf__, 2, 3))) +#endif +static void +indented_printf(unsigned indentLevel, const char *fmt, ...) { + for (unsigned i = 0; i < indentLevel; i++) + fputs(" ", stdout); + + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + static const size_t ReadEnd = 0; static const size_t WriteEnd = 1; @@ -774,10 +789,40 @@ int reflectEnumValue(SwiftReflectionContextRef RC, } -int reflectAsyncTask(SwiftReflectionContextRef RC, - const PipeMemoryReader *Reader) { - uintptr_t AsyncTaskInstance = PipeMemoryReader_receiveInstanceAddress(Reader); - printf("Async task %#" PRIx64 "\n", (uint64_t)AsyncTaskInstance); +static int reflectAsyncTaskInstance(SwiftReflectionContextRef RC, + uintptr_t AsyncTaskInstance, + const PipeMemoryReader *Reader, + unsigned indentLevel) { + indented_printf(indentLevel, "Async task %#" PRIx64 "\n", + (uint64_t)AsyncTaskInstance); + + swift_async_task_info_t TaskInfo = + swift_reflection_asyncTaskInfo(RC, AsyncTaskInstance); + if (TaskInfo.Error) { + printf("swift_reflection_asyncTaskInfo failed: %s\n", TaskInfo.Error); + } else { + indented_printf(indentLevel, "id %" PRIu64 "\n", TaskInfo.Id); + indented_printf(indentLevel, "enqueuePriority %u\n", + TaskInfo.EnqueuePriority); + if (TaskInfo.ChildTaskCount > 0) { + indented_printf(indentLevel, "children = {\n"); + + // The memory for ChildTasks is only valid until the next Remote Mirror + // call, so we need to copy it. + swift_reflection_ptr_t *ChildTasks = + calloc(TaskInfo.ChildTaskCount, sizeof(swift_reflection_ptr_t)); + memcpy(ChildTasks, TaskInfo.ChildTasks, + TaskInfo.ChildTaskCount * sizeof(swift_reflection_ptr_t)); + + for (unsigned i = 0; i < TaskInfo.ChildTaskCount; i++) + reflectAsyncTaskInstance(RC, ChildTasks[i], Reader, indentLevel + 1); + + free(ChildTasks); + indented_printf(indentLevel, "}\n"); + } else { + indented_printf(indentLevel, "children = {}\n"); + } + } swift_async_task_slab_return_t SlabPtrResult = swift_reflection_asyncTaskSlabPointer(RC, AsyncTaskInstance); @@ -787,33 +832,67 @@ int reflectAsyncTask(SwiftReflectionContextRef RC, } else { swift_reflection_ptr_t SlabPtr = SlabPtrResult.SlabPtr; while (SlabPtr) { - printf(" Slab pointer %#" PRIx64 "\n", (uint64_t)SlabPtr); + indented_printf(indentLevel, " Slab pointer %#" PRIx64 "\n", + (uint64_t)SlabPtr); swift_async_task_slab_allocations_return_t AllocationsResult = swift_reflection_asyncTaskSlabAllocations(RC, SlabPtr); if (AllocationsResult.Error) { - printf("swift_reflection_asyncTaskSlabAllocations failed: %s\n", - AllocationsResult.Error); + indented_printf( + indentLevel, + "swift_reflection_asyncTaskSlabAllocations failed: %s\n", + AllocationsResult.Error); SlabPtr = 0; } else { - printf(" Slab size %" PRIu64 "\n", - (uint64_t)AllocationsResult.SlabSize); + indented_printf(indentLevel, " Slab size %" PRIu64 "\n", + (uint64_t)AllocationsResult.SlabSize); for (unsigned i = 0; i < AllocationsResult.ChunkCount; i++) { swift_async_task_allocation_chunk_t Chunk = AllocationsResult.Chunks[i]; - printf(" Chunk at %#" PRIx64 " length %u kind %u\n", - (uint64_t)Chunk.Start, Chunk.Length, Chunk.Kind); + indented_printf(indentLevel, + " Chunk at %#" PRIx64 " length %u kind %u\n", + (uint64_t)Chunk.Start, Chunk.Length, Chunk.Kind); } SlabPtr = AllocationsResult.NextSlab; } } } - printf("\n\n"); - PipeMemoryReader_sendDoneMessage(Reader); + if (indentLevel == 0) { + printf("\n\n"); + } fflush(stdout); return 1; } +int reflectAsyncTask(SwiftReflectionContextRef RC, + const PipeMemoryReader *Reader) { + uintptr_t AsyncTaskInstance = PipeMemoryReader_receiveInstanceAddress(Reader); + int result = reflectAsyncTaskInstance(RC, AsyncTaskInstance, Reader, 0); + PipeMemoryReader_sendDoneMessage(Reader); + return result; +} + +int logString(SwiftReflectionContextRef RC, const PipeMemoryReader *Reader) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" + void *Context = (void *)Reader; +#pragma clang diagnostic pop + + swift_addr_t StringPointer = PipeMemoryReader_receiveInstanceAddress(Context); + uint64_t StringLength = + PipeMemoryReader_getStringLength(Context, StringPointer); + + void *FreeContext; + // Read length+1 bytes to get the NUL terminator too. + const void *String = PipeMemoryReader_readBytes( + Context, StringPointer, StringLength + 1, &FreeContext); + + printf("%s\n", (const char *)String); + PipeMemoryReader_freeBytes(Context, String, FreeContext); + + PipeMemoryReader_sendDoneMessage(Context); + return 1; +} int doDumpHeapInstance(const char *BinaryFilename, PipeMemoryReader *Reader) { #if defined(_WIN32) @@ -926,6 +1005,11 @@ int doDumpHeapInstance(const char *BinaryFilename, PipeMemoryReader *Reader) { return EXIT_SUCCESS; break; } + case LogString: { + if (!logString(RC, Reader)) + return EXIT_SUCCESS; + break; + } case None: swift_reflection_destroyReflectionContext(RC); printf("Done.\n"); diff --git a/test/Concurrency/Reflection/reflect_task.swift b/test/Concurrency/Reflection/reflect_task.swift index 0e548960d8e1d..50b986be6315a 100644 --- a/test/Concurrency/Reflection/reflect_task.swift +++ b/test/Concurrency/Reflection/reflect_task.swift @@ -28,28 +28,72 @@ func _getCurrentTaskShim() -> UInt func add(_ a: UInt, _ b: UInt) async -> UInt { if b == 0 { reflect(asyncTask: _getCurrentTaskShim()) - // CHECK: Reflecting an async task. - // CHECK: Async task {{0x[0-9a-fA-F]*}} - - // The actual number of chunks we'll get depends on internal implementation - // details that we don't want this test to depend on. We'll just make sure - // we get at least two, and ignore the details. - // CHECK: Slab pointer {{0x[0-9a-fA-F]*}} - // CHECK: Slab size {{[0-9]{2,}()}} - // CHECK: Chunk at {{0x[0-9a-fA-F]*}} length {{[1-9][0-9]*}} kind {{[0-9]*}} - // CHECK: Slab pointer {{0x[0-9a-fA-F]*}} - // CHECK: Slab size {{[0-9]{2,}()}} - // CHECK: Chunk at {{0x[0-9a-fA-F]*}} length {{[1-9[[0-9]*}} kind {{[0-9]*}} return a } else { return await add(a, b - 1) + 1 } } +func sleepForever() async -> Int { + if #available(SwiftStdlib 5.7, *) { + try? await Task.sleep(for: .seconds(1_000_000_000)) + return 42 + } else { + fatalError("This test shouldn't be running against old stdlibs.") + } +} + +func testNestedCallsTask() async { + reflectionLog(str: "testNestedCallsTask") + // CHECK: testNestedCallsTask + + let n = await add(100, 100) + reflect(any: n) + // CHECK: Reflecting an async task. + // CHECK: Async task {{0x[0-9a-fA-F]*}} + + // The actual number of chunks we'll get depends on internal implementation + // details that we don't want this test to depend on. We'll just make sure + // we get at least two, and ignore the details. + // CHECK: Slab pointer {{0x[0-9a-fA-F]*}} + // CHECK: Slab size {{[0-9]{2,}()}} + // CHECK: Chunk at {{0x[0-9a-fA-F]*}} length {{[1-9][0-9]*}} kind {{[0-9]*}} + // CHECK: Slab pointer {{0x[0-9a-fA-F]*}} + // CHECK: Slab size {{[0-9]{2,}()}} + // CHECK: Chunk at {{0x[0-9a-fA-F]*}} length {{[1-9[[0-9]*}} kind {{[0-9]*}} +} + +func testOneAsyncLet() async { + reflectionLog(str: "testOneAsyncLet") + // CHECK: testOneAsyncLet + + async let alet = sleepForever() + reflect(asyncTask: _getCurrentTaskShim()) + // CHECK: Async task {{0x[0-9a-fA-F]*}} + // CHECK: children = { + // CHECK: Async task {{0x[0-9a-fA-F]*}} + // CHECK: } +} + +func testMultipleAsyncLet() async { + reflectionLog(str: "testMultipleAsyncLet") + // CHECK: testMultipleAsyncLet + + async let alet1 = sleepForever() + async let alet2 = sleepForever() + reflect(asyncTask: _getCurrentTaskShim()) + // CHECK: Async task {{0x[0-9a-fA-F]*}} + // CHECK: children = { + // CHECK: Async task {{0x[0-9a-fA-F]*}} + // CHECK: Async task {{0x[0-9a-fA-F]*}} + // CHECK: } +} + @main struct Main { static func main() async { - let n = await add(100, 100) - reflect(any: n) + await testNestedCallsTask() + await testOneAsyncLet() + await testMultipleAsyncLet() doneReflecting() } diff --git a/test/abi/Inputs/macOS/arm64/concurrency/baseline b/test/abi/Inputs/macOS/arm64/concurrency/baseline index 557c626a2cada..b6ed4a9a6bc9a 100644 --- a/test/abi/Inputs/macOS/arm64/concurrency/baseline +++ b/test/abi/Inputs/macOS/arm64/concurrency/baseline @@ -1160,6 +1160,7 @@ _$ss9TaskLocalCyxGs23CustomStringConvertiblesMc _$ss9_contains_5whereSbx_Sb7ElementQzYaKXEtYaKSciRzlF _$ss9_contains_5whereSbx_Sb7ElementQzYaKXEtYaKSciRzlFTu __swift_concurrency_debug_asyncTaskMetadata +__swift_concurrency_debug_asyncTaskSize __swift_concurrency_debug_asyncTaskSlabMetadata __swift_concurrency_debug_future_adapter __swift_concurrency_debug_jobMetadata diff --git a/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts b/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts index 557c626a2cada..b6ed4a9a6bc9a 100644 --- a/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts +++ b/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts @@ -1160,6 +1160,7 @@ _$ss9TaskLocalCyxGs23CustomStringConvertiblesMc _$ss9_contains_5whereSbx_Sb7ElementQzYaKXEtYaKSciRzlF _$ss9_contains_5whereSbx_Sb7ElementQzYaKXEtYaKSciRzlFTu __swift_concurrency_debug_asyncTaskMetadata +__swift_concurrency_debug_asyncTaskSize __swift_concurrency_debug_asyncTaskSlabMetadata __swift_concurrency_debug_future_adapter __swift_concurrency_debug_jobMetadata diff --git a/test/abi/Inputs/macOS/x86_64/concurrency/baseline b/test/abi/Inputs/macOS/x86_64/concurrency/baseline index 557c626a2cada..b6ed4a9a6bc9a 100644 --- a/test/abi/Inputs/macOS/x86_64/concurrency/baseline +++ b/test/abi/Inputs/macOS/x86_64/concurrency/baseline @@ -1160,6 +1160,7 @@ _$ss9TaskLocalCyxGs23CustomStringConvertiblesMc _$ss9_contains_5whereSbx_Sb7ElementQzYaKXEtYaKSciRzlF _$ss9_contains_5whereSbx_Sb7ElementQzYaKXEtYaKSciRzlFTu __swift_concurrency_debug_asyncTaskMetadata +__swift_concurrency_debug_asyncTaskSize __swift_concurrency_debug_asyncTaskSlabMetadata __swift_concurrency_debug_future_adapter __swift_concurrency_debug_jobMetadata diff --git a/test/abi/Inputs/macOS/x86_64/concurrency/baseline-asserts b/test/abi/Inputs/macOS/x86_64/concurrency/baseline-asserts index 557c626a2cada..b6ed4a9a6bc9a 100644 --- a/test/abi/Inputs/macOS/x86_64/concurrency/baseline-asserts +++ b/test/abi/Inputs/macOS/x86_64/concurrency/baseline-asserts @@ -1160,6 +1160,7 @@ _$ss9TaskLocalCyxGs23CustomStringConvertiblesMc _$ss9_contains_5whereSbx_Sb7ElementQzYaKXEtYaKSciRzlF _$ss9_contains_5whereSbx_Sb7ElementQzYaKXEtYaKSciRzlFTu __swift_concurrency_debug_asyncTaskMetadata +__swift_concurrency_debug_asyncTaskSize __swift_concurrency_debug_asyncTaskSlabMetadata __swift_concurrency_debug_future_adapter __swift_concurrency_debug_jobMetadata