Skip to content

Commit b9a2573

Browse files
authoredOct 3, 2021
Experimental global executor cooperating with JS event loop (swiftwasm#141)
* Experimental global executor cooperating with JS event loop * Run Concurrency tests on CI * Skip concurrency test for older toolchains * Add more tests for concurrency * Use the latest snapshot * Add missing compile-time condition to support older toolchain
1 parent aa521a1 commit b9a2573

File tree

12 files changed

+564
-3
lines changed

12 files changed

+564
-3
lines changed
 

‎.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
toolchain:
1313
- wasm-5.3.1-RELEASE
1414
- wasm-5.4.0-RELEASE
15-
- wasm-5.5-SNAPSHOT-2021-09-01-a
15+
- wasm-5.5-SNAPSHOT-2021-10-02-a
1616
runs-on: ${{ matrix.os }}
1717
steps:
1818
- name: Checkout

‎IntegrationTests/Makefile

+9-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ run_benchmark:
3030
.PHONY: benchmark
3131
benchmark: benchmark_setup run_benchmark
3232

33-
.PHONY: test
34-
test: build_rt dist/PrimaryTests.wasm
33+
.PHONY: primary_test
34+
primary_test: build_rt dist/PrimaryTests.wasm
3535
node bin/primary-tests.js
36+
37+
.PHONY: concurrency_test
38+
concurrency_test: build_rt dist/ConcurrencyTests.wasm
39+
node bin/concurrency-tests.js
40+
41+
.PHONY: test
42+
test: concurrency_test primary_test

‎IntegrationTests/TestSuites/Package.swift

+15
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,32 @@ import PackageDescription
44

55
let package = Package(
66
name: "TestSuites",
7+
platforms: [
8+
// This package doesn't work on macOS host, but should be able to be built for it
9+
// for developing on Xcode. This minimum version requirement is to prevent availability
10+
// errors for Concurrency API, whose runtime support is shipped from macOS 12.0
11+
.macOS("12.0")
12+
],
713
products: [
814
.executable(
915
name: "PrimaryTests", targets: ["PrimaryTests"]
1016
),
17+
.executable(
18+
name: "ConcurrencyTests", targets: ["ConcurrencyTests"]
19+
),
1120
.executable(
1221
name: "BenchmarkTests", targets: ["BenchmarkTests"]
1322
),
1423
],
1524
dependencies: [.package(name: "JavaScriptKit", path: "../../")],
1625
targets: [
1726
.target(name: "PrimaryTests", dependencies: ["JavaScriptKit"]),
27+
.target(
28+
name: "ConcurrencyTests",
29+
dependencies: [
30+
.product(name: "JavaScriptEventLoop", package: "JavaScriptKit"),
31+
]
32+
),
1833
.target(name: "BenchmarkTests", dependencies: ["JavaScriptKit"]),
1934
]
2035
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import JavaScriptKit
2+
3+
#if compiler(>=5.5)
4+
var printTestNames = false
5+
// Uncomment the next line to print the name of each test suite before running it.
6+
// This will make it easier to debug any errors that occur on the JS side.
7+
//printTestNames = true
8+
9+
func test(_ name: String, testBlock: () throws -> Void) throws {
10+
if printTestNames { print(name) }
11+
do {
12+
try testBlock()
13+
} catch {
14+
print("Error in \(name)")
15+
print(error)
16+
throw error
17+
}
18+
}
19+
20+
func asyncTest(_ name: String, testBlock: () async throws -> Void) async throws -> Void {
21+
if printTestNames { print(name) }
22+
do {
23+
try await testBlock()
24+
} catch {
25+
print("Error in \(name)")
26+
print(error)
27+
throw error
28+
}
29+
}
30+
31+
struct MessageError: Error {
32+
let message: String
33+
let file: StaticString
34+
let line: UInt
35+
let column: UInt
36+
init(_ message: String, file: StaticString, line: UInt, column: UInt) {
37+
self.message = message
38+
self.file = file
39+
self.line = line
40+
self.column = column
41+
}
42+
}
43+
44+
func expectEqual<T: Equatable>(
45+
_ lhs: T, _ rhs: T,
46+
file: StaticString = #file, line: UInt = #line, column: UInt = #column
47+
) throws {
48+
if lhs != rhs {
49+
throw MessageError("Expect to be equal \"\(lhs)\" and \"\(rhs)\"", file: file, line: line, column: column)
50+
}
51+
}
52+
53+
func expectCast<T, U>(
54+
_ value: T, to type: U.Type = U.self,
55+
file: StaticString = #file, line: UInt = #line, column: UInt = #column
56+
) throws -> U {
57+
guard let value = value as? U else {
58+
throw MessageError("Expect \"\(value)\" to be \(U.self)", file: file, line: line, column: column)
59+
}
60+
return value
61+
}
62+
63+
func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObject {
64+
switch value {
65+
case let .object(ref): return ref
66+
default:
67+
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column)
68+
}
69+
}
70+
71+
func expectArray(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSArray {
72+
guard let array = value.array else {
73+
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column)
74+
}
75+
return array
76+
}
77+
78+
func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunction {
79+
switch value {
80+
case let .function(ref): return ref
81+
default:
82+
throw MessageError("Type of \(value) should be \"function\"", file: file, line: line, column: column)
83+
}
84+
}
85+
86+
func expectBoolean(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Bool {
87+
switch value {
88+
case let .boolean(bool): return bool
89+
default:
90+
throw MessageError("Type of \(value) should be \"boolean\"", file: file, line: line, column: column)
91+
}
92+
}
93+
94+
func expectNumber(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Double {
95+
switch value {
96+
case let .number(number): return number
97+
default:
98+
throw MessageError("Type of \(value) should be \"number\"", file: file, line: line, column: column)
99+
}
100+
}
101+
102+
func expectString(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> String {
103+
switch value {
104+
case let .string(string): return String(string)
105+
default:
106+
throw MessageError("Type of \(value) should be \"string\"", file: file, line: line, column: column)
107+
}
108+
}
109+
110+
func expectAsyncThrow<T>(_ body: @autoclosure () async throws -> T, file: StaticString = #file, line: UInt = #line, column: UInt = #column) async throws -> Error {
111+
do {
112+
_ = try await body()
113+
} catch {
114+
return error
115+
}
116+
throw MessageError("Expect to throw an exception", file: file, line: line, column: column)
117+
}
118+
119+
func expectNotNil<T>(_ value: T?, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws {
120+
switch value {
121+
case .some: return
122+
case .none:
123+
throw MessageError("Expect a non-nil value", file: file, line: line, column: column)
124+
}
125+
}
126+
127+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import JavaScriptEventLoop
2+
import JavaScriptKit
3+
#if canImport(WASILibc)
4+
import WASILibc
5+
#elseif canImport(Darwin)
6+
import Darwin
7+
#endif
8+
9+
#if compiler(>=5.5)
10+
11+
func entrypoint() async throws {
12+
struct E: Error, Equatable {
13+
let value: Int
14+
}
15+
16+
try await asyncTest("Task.init value") {
17+
let handle = Task { 1 }
18+
try expectEqual(await handle.value, 1)
19+
}
20+
21+
try await asyncTest("Task.init throws") {
22+
let handle = Task {
23+
throw E(value: 2)
24+
}
25+
let error = try await expectAsyncThrow(await handle.value)
26+
let e = try expectCast(error, to: E.self)
27+
try expectEqual(e, E(value: 2))
28+
}
29+
30+
try await asyncTest("await resolved Promise") {
31+
let p = JSPromise(resolver: { resolve in
32+
resolve(.success(1))
33+
})
34+
try await expectEqual(p.value, 1)
35+
}
36+
37+
try await asyncTest("await rejected Promise") {
38+
let p = JSPromise(resolver: { resolve in
39+
resolve(.failure(.number(3)))
40+
})
41+
let error = try await expectAsyncThrow(await p.value)
42+
let jsValue = try expectCast(error, to: JSValue.self)
43+
try expectEqual(jsValue, 3)
44+
}
45+
46+
try await asyncTest("Continuation") {
47+
let value = await withUnsafeContinuation { cont in
48+
cont.resume(returning: 1)
49+
}
50+
try expectEqual(value, 1)
51+
52+
let error = try await expectAsyncThrow(
53+
try await withUnsafeThrowingContinuation { (cont: UnsafeContinuation<Never, Error>) in
54+
cont.resume(throwing: E(value: 2))
55+
}
56+
)
57+
let e = try expectCast(error, to: E.self)
58+
try expectEqual(e.value, 2)
59+
}
60+
61+
try await asyncTest("Task.sleep(_:)") {
62+
let start = time(nil)
63+
await Task.sleep(2_000_000_000)
64+
let diff = difftime(time(nil), start);
65+
try expectEqual(diff >= 2, true)
66+
}
67+
68+
try await asyncTest("Job reordering based on priority") {
69+
class Context: @unchecked Sendable {
70+
var completed: [String] = []
71+
}
72+
let context = Context()
73+
74+
// When no priority, they should be ordered by the enqueued order
75+
let t1 = Task(priority: nil) {
76+
context.completed.append("t1")
77+
}
78+
let t2 = Task(priority: nil) {
79+
context.completed.append("t2")
80+
}
81+
82+
_ = await (t1.value, t2.value)
83+
try expectEqual(context.completed, ["t1", "t2"])
84+
85+
context.completed = []
86+
// When high priority is enqueued after a low one, they should be re-ordered
87+
let t3 = Task(priority: .low) {
88+
context.completed.append("t3")
89+
}
90+
let t4 = Task(priority: .high) {
91+
context.completed.append("t4")
92+
}
93+
let t5 = Task(priority: .low) {
94+
context.completed.append("t5")
95+
}
96+
97+
_ = await (t3.value, t4.value, t5.value)
98+
try expectEqual(context.completed, ["t4", "t3", "t5"])
99+
}
100+
// FIXME(katei): Somehow it doesn't work due to a mysterious unreachable inst
101+
// at the end of thunk.
102+
// This issue is not only on JS host environment, but also on standalone coop executor.
103+
// try await asyncTest("Task.sleep(nanoseconds:)") {
104+
// try await Task.sleep(nanoseconds: 1_000_000_000)
105+
// }
106+
}
107+
108+
109+
// Note: Please define `USE_SWIFT_TOOLS_VERSION_NEWER_THAN_5_5` if the swift-tools-version is newer
110+
// than 5.5 to avoid the linking issue.
111+
#if USE_SWIFT_TOOLS_VERSION_NEWER_THAN_5_5
112+
// Workaround: The latest SwiftPM rename main entry point name of executable target
113+
// to avoid conflicting "main" with test target since `swift-tools-version >= 5.5`.
114+
// The main symbol is renamed to "{{module_name}}_main" and it's renamed again to be
115+
// "main" when linking the executable target. The former renaming is done by Swift compiler,
116+
// and the latter is done by linker, so SwiftPM passes some special linker flags for each platform.
117+
// But SwiftPM assumes that wasm-ld supports it by returning an empty array instead of nil even though
118+
// wasm-ld doesn't support it yet.
119+
// ref: https://github.com/apple/swift-package-manager/blob/1be68e811d0d814ba7abbb8effee45f1e8e6ec0d/Sources/Build/BuildPlan.swift#L117-L126
120+
// So define an explicit "main" by @_cdecl
121+
@_cdecl("main")
122+
func main(argc: Int32, argv: Int32) -> Int32 {
123+
JavaScriptEventLoop.installGlobalExecutor()
124+
Task {
125+
do {
126+
try await entrypoint()
127+
} catch {
128+
print(error)
129+
}
130+
}
131+
return 0
132+
}
133+
#else
134+
JavaScriptEventLoop.installGlobalExecutor()
135+
Task {
136+
do {
137+
try await entrypoint()
138+
} catch {
139+
print(error)
140+
}
141+
}
142+
143+
#endif
144+
145+
146+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const { startWasiTask } = require("../lib");
2+
3+
startWasiTask("./dist/ConcurrencyTests.wasm").catch((err) => {
4+
console.log(err);
5+
process.exit(1);
6+
});

‎Package.swift

+12
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,26 @@ import PackageDescription
44

55
let package = Package(
66
name: "JavaScriptKit",
7+
platforms: [
8+
// This package doesn't work on macOS host, but should be able to be built for it
9+
// for developing on Xcode. This minimum version requirement is to prevent availability
10+
// errors for Concurrency API, whose runtime support is shipped from macOS 12.0
11+
.macOS("12.0")
12+
],
713
products: [
814
.library(name: "JavaScriptKit", targets: ["JavaScriptKit"]),
15+
.library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]),
916
],
1017
targets: [
1118
.target(
1219
name: "JavaScriptKit",
1320
dependencies: ["_CJavaScriptKit"]
1421
),
1522
.target(name: "_CJavaScriptKit"),
23+
.target(
24+
name: "JavaScriptEventLoop",
25+
dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"]
26+
),
27+
.target(name: "_CJavaScriptEventLoop"),
1628
]
1729
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import JavaScriptKit
2+
import _CJavaScriptEventLoop
3+
4+
#if compiler(>=5.5)
5+
6+
public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
7+
8+
/// A function that queues a given closure as a microtask into JavaScript event loop.
9+
/// See also: https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
10+
let queueMicrotask: @Sendable (@escaping () -> Void) -> Void
11+
/// A function that invokes a given closure after a specified number of milliseconds.
12+
let setTimeout: @Sendable (UInt64, @escaping () -> Void) -> Void
13+
14+
/// A mutable state to manage internal job queue
15+
/// Note that this should be guarded atomically when supporting multi-threaded environment.
16+
var queueState = QueueState()
17+
18+
private init(
19+
queueTask: @Sendable @escaping (@escaping () -> Void) -> Void,
20+
setTimeout: @Sendable @escaping (UInt64, @escaping () -> Void) -> Void
21+
) {
22+
self.queueMicrotask = queueTask
23+
self.setTimeout = setTimeout
24+
}
25+
26+
/// A singleton instance of the Executor
27+
public static let shared: JavaScriptEventLoop = {
28+
let promise = JSPromise(resolver: { resolver -> Void in
29+
resolver(.success(.undefined))
30+
})
31+
let setTimeout = JSObject.global.setTimeout.function!
32+
let eventLoop = JavaScriptEventLoop(
33+
queueTask: { job in
34+
// TODO(katei): Should prefer `queueMicrotask` if available?
35+
// We should measure if there is performance advantage.
36+
promise.then { _ in
37+
job()
38+
return JSValue.undefined
39+
}
40+
},
41+
setTimeout: { delay, job in
42+
setTimeout(JSOneshotClosure { _ in
43+
job()
44+
return JSValue.undefined
45+
}, delay)
46+
}
47+
)
48+
return eventLoop
49+
}()
50+
51+
/// Set JavaScript event loop based executor to be the global executor
52+
/// Note that this should be called before any of the jobs are created.
53+
/// This installation step will be unnecessary after the custom-executor will be introduced officially.
54+
/// See also: https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md#the-default-global-concurrent-executor
55+
public static func installGlobalExecutor() {
56+
typealias swift_task_enqueueGlobal_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) -> Void
57+
let swift_task_enqueueGlobal_hook_impl: swift_task_enqueueGlobal_hook_Fn = { job, original in
58+
JavaScriptEventLoop.shared.enqueue(job)
59+
}
60+
swift_task_enqueueGlobal_hook = unsafeBitCast(swift_task_enqueueGlobal_hook_impl, to: UnsafeMutableRawPointer?.self)
61+
62+
typealias swift_task_enqueueGlobalWithDelay_hook_Fn = @convention(thin) (UInt64, UnownedJob, swift_task_enqueueGlobalWithDelay_original) -> Void
63+
let swift_task_enqueueGlobalWithDelay_hook_impl: swift_task_enqueueGlobalWithDelay_hook_Fn = { delay, job, original in
64+
JavaScriptEventLoop.shared.enqueue(job, withDelay: delay)
65+
}
66+
swift_task_enqueueGlobalWithDelay_hook = unsafeBitCast(swift_task_enqueueGlobalWithDelay_hook_impl, to: UnsafeMutableRawPointer?.self)
67+
68+
typealias swift_task_enqueueMainExecutor_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueMainExecutor_original) -> Void
69+
let swift_task_enqueueMainExecutor_hook_impl: swift_task_enqueueMainExecutor_hook_Fn = { job, original in
70+
JavaScriptEventLoop.shared.enqueue(job)
71+
}
72+
swift_task_enqueueMainExecutor_hook = unsafeBitCast(swift_task_enqueueMainExecutor_hook_impl, to: UnsafeMutableRawPointer?.self)
73+
}
74+
75+
private func enqueue(_ job: UnownedJob, withDelay nanoseconds: UInt64) {
76+
let milliseconds = nanoseconds / 1_000_000
77+
setTimeout(milliseconds, {
78+
job._runSynchronously(on: self.asUnownedSerialExecutor())
79+
})
80+
}
81+
82+
public func enqueue(_ job: UnownedJob) {
83+
insertJobQueue(job: job)
84+
}
85+
86+
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
87+
return UnownedSerialExecutor(ordinary: self)
88+
}
89+
}
90+
91+
public extension JSPromise {
92+
/// Wait for the promise to complete, returning (or throwing) its result.
93+
var value: JSValue {
94+
get async throws {
95+
try await withUnsafeThrowingContinuation { [self] continuation in
96+
self.then(
97+
success: {
98+
continuation.resume(returning: $0)
99+
return JSValue.undefined
100+
},
101+
failure: {
102+
continuation.resume(throwing: $0)
103+
return JSValue.undefined
104+
}
105+
)
106+
}
107+
}
108+
}
109+
}
110+
111+
#endif
+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// This file contains the job queue implementation which re-order jobs based on their priority.
2+
// The current implementation is much simple to be easily debugged, but should be re-implemented
3+
// using priority queue ideally.
4+
5+
import _CJavaScriptEventLoop
6+
7+
#if compiler(>=5.5)
8+
struct QueueState: Sendable {
9+
fileprivate var headJob: UnownedJob? = nil
10+
fileprivate var isSpinning: Bool = false
11+
}
12+
13+
extension JavaScriptEventLoop {
14+
15+
func insertJobQueue(job newJob: UnownedJob) {
16+
withUnsafeMutablePointer(to: &queueState.headJob) { headJobPtr in
17+
var position: UnsafeMutablePointer<UnownedJob?> = headJobPtr
18+
while let cur = position.pointee {
19+
if cur.rawPriority < newJob.rawPriority {
20+
newJob.nextInQueue().pointee = cur
21+
position.pointee = newJob
22+
return
23+
}
24+
position = cur.nextInQueue()
25+
}
26+
newJob.nextInQueue().pointee = nil
27+
position.pointee = newJob
28+
}
29+
30+
// TODO: use CAS when supporting multi-threaded environment
31+
if !queueState.isSpinning {
32+
self.queueState.isSpinning = true
33+
JavaScriptEventLoop.shared.queueMicrotask {
34+
self.runAllJobs()
35+
}
36+
}
37+
}
38+
39+
func runAllJobs() {
40+
assert(queueState.isSpinning)
41+
42+
while let job = self.claimNextFromQueue() {
43+
job._runSynchronously(on: self.asUnownedSerialExecutor())
44+
}
45+
46+
queueState.isSpinning = false
47+
}
48+
49+
func claimNextFromQueue() -> UnownedJob? {
50+
if let job = self.queueState.headJob {
51+
self.queueState.headJob = job.nextInQueue().pointee
52+
return job
53+
}
54+
return nil
55+
}
56+
}
57+
58+
fileprivate extension UnownedJob {
59+
private func asImpl() -> UnsafeMutablePointer<_CJavaScriptEventLoop.Job> {
60+
unsafeBitCast(self, to: UnsafeMutablePointer<_CJavaScriptEventLoop.Job>.self)
61+
}
62+
63+
var flags: JobFlags {
64+
JobFlags(bits: asImpl().pointee.Flags)
65+
}
66+
67+
var rawPriority: UInt32 { flags.priority }
68+
69+
func nextInQueue() -> UnsafeMutablePointer<UnownedJob?> {
70+
return withUnsafeMutablePointer(to: &asImpl().pointee.SchedulerPrivate.0) { rawNextJobPtr in
71+
let nextJobPtr = UnsafeMutableRawPointer(rawNextJobPtr).bindMemory(to: UnownedJob?.self, capacity: 1)
72+
return nextJobPtr
73+
}
74+
}
75+
76+
}
77+
78+
fileprivate struct JobFlags {
79+
var bits: UInt32 = 0
80+
81+
var priority: UInt32 {
82+
get {
83+
(bits & 0xFF00) >> 8
84+
}
85+
}
86+
}
87+
#endif

‎Sources/_CJavaScriptEventLoop/_CJavaScriptEventLoop.c

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#ifndef _CJavaScriptEventLoop_h
2+
#define _CJavaScriptEventLoop_h
3+
4+
#include <stdalign.h>
5+
#include <stdint.h>
6+
7+
#define SWIFT_CC(CC) SWIFT_CC_##CC
8+
#define SWIFT_CC_swift __attribute__((swiftcall))
9+
10+
#define SWIFT_EXPORT_FROM(LIBRARY) __attribute__((__visibility__("default")))
11+
12+
/// A schedulable unit
13+
/// Note that this type layout is a part of public ABI, so we expect this field layout won't break in the future versions.
14+
/// Current implementation refers the `swift-5.5-RELEASE` implementation.
15+
/// https://github.com/apple/swift/blob/swift-5.5-RELEASE/include/swift/ABI/Task.h#L43-L129
16+
/// This definition is used to retrieve priority value of a job. After custom-executor API will be introduced officially,
17+
/// the job priority API will be provided in the Swift world.
18+
typedef __attribute__((aligned(2 * alignof(void *)))) struct {
19+
void *_Nonnull Metadata;
20+
int32_t RefCounts;
21+
void *_Nullable SchedulerPrivate[2];
22+
uint32_t Flags;
23+
} Job;
24+
25+
/// A hook to take over global enqueuing.
26+
typedef SWIFT_CC(swift) void (*swift_task_enqueueGlobal_original)(
27+
Job *_Nonnull job);
28+
29+
SWIFT_EXPORT_FROM(swift_Concurrency)
30+
void *_Nullable swift_task_enqueueGlobal_hook;
31+
32+
/// A hook to take over global enqueuing with delay.
33+
typedef SWIFT_CC(swift) void (*swift_task_enqueueGlobalWithDelay_original)(
34+
unsigned long long delay, Job *_Nonnull job);
35+
SWIFT_EXPORT_FROM(swift_Concurrency)
36+
void *_Nullable swift_task_enqueueGlobalWithDelay_hook;
37+
38+
unsigned long long foo;
39+
40+
/// A hook to take over main executor enqueueing.
41+
typedef SWIFT_CC(swift) void (*swift_task_enqueueMainExecutor_original)(
42+
Job *_Nonnull job);
43+
SWIFT_EXPORT_FROM(swift_Concurrency)
44+
void *_Nullable swift_task_enqueueMainExecutor_hook;
45+
46+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module _CJavaScriptEventLoop {
2+
header "_CJavaScriptEventLoop.h"
3+
export *
4+
}

0 commit comments

Comments
 (0)
Please sign in to comment.