Hi, I'm on vacation and can't quite answer in depth, but this proposal 
encourages all the wrong things in terms of performance. We know from 10 years 
of GCD that mixing async/sync, while possible, yields significant performance 
issues, and I'd rather not have something at the core of the language 
encouraging it.

Actors seem like a much better way of solving these problems IMO.

-Pierre

> On Nov 16, 2017, at 1:50 PM, Christopher Heath via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
> Good evening all,
> 
> I had a little idea and thought I’d pitch it. So here goes!
> 
> Synthesizing Concurrency aims to provide a foundation by which, regardless of 
> concurrency implementation (however current proposal uses GCD examples for 
> design), concurrency can be synthesized by the compiler and remove many lines 
> of boilerplate code. Offering benefits to Application, Server and OS 
> Developers alike; with easy and intelligent concurrency conformance. Also, 
> should the community decide to head in another direction with concurrency 
> this should be a relatively mappable idea to any Synchronization Primitive 
> with a little work.
> 
> Thanks for looking, and I hope you like and want to contribute to the 
> discussion of the Synthesizing Concurrency proposal. I’ve added a printout 
> below. Please give it a read or check out the gist: 
> https://gist.github.com/XNUPlay/a0d6f6c0afdb3286e324c480cb5c4290 
> <https://gist.github.com/XNUPlay/a0d6f6c0afdb3286e324c480cb5c4290>
> Chris
> 
> Printout :
> Synthesizing Concurrency
> Proposal: SE-NNNN 
> <https://gist.github.com/XNUPlay/a0d6f6c0afdb3286e324c480cb5c4290>
> Author: Christopher Heath: XNUPlay <https://github.com/xnuplay>
> Review Manager: TBD
> Status: Pending Discussion
> Implementation: Awaiting Implementation
> Decision Notes: TBD
> 
> Introduction
> Developers have to write large amounts of boilerplate code to support 
> concurrency in complex types. This proposal offers a way for the compiler to 
> automatically synthesize conformance to a high-level Concurrent protocol to 
> reduce concurrent boilerplate code, in a set of scenarios where generating 
> the correct implementation is known to be possible.
> Specifically:
> It aims to provide a high-level Swift protocol that offers opt-in concurrency 
> support for any conformable type
> It aims to provide a well-defined set of thread-safe, concurrent 
> implementations for types, their properties, and methods.
> It aims to provide a language/library compatible implementation with deadlock 
> prevention.
> 
> Motivation
> Building robust types in Swift can involve writing significant boilerplate 
> code to support concurrency. By eliminating the complexity for the users, we 
> make Concurrent types much more appealing to users and allow them to use 
> their own types in optimized concurrent and parallel environments that 
> require thread safety with no added effort on their part (beyond declaring 
> the conformance).
> Concurrency is typically not pervasive across many types, and for each one 
> users must implement the concurrent code such that it performs some form of 
> synchronization to prevent unexpected behavior.
> 
> Note: Due to it's current status in Swift and use in the Runtime 
> <https://github.com/apple/swift/blob/master/stdlib/public/runtime/Once.cpp#L31>,
>  examples are written in Grand Central Dispatch 
> <https://github.com/apple/swift-corelibs-libdispatch>
> 
> // Concurrent Protocol - Dispatch Example
> protocol Concurrent {
>     // Synthesized Property
>     var internalQueue: DispatchQueue { get }
> }
> 
> What's worse is that if any functions or properties are added, removed, or 
> changed, they must each have their own concurrency code and since it must be 
> manually written, it's possible to get it wrong, either by omission or 
> typographical error (async vs. sync).
> Likewise, it becomes necessary when one wishes to modify an existing type 
> that implements concurrency to do so without introducing bottlenecks or 
> different forms of synchronization for some functions and not others, this 
> leads to illegible and inefficient code that may defeat the purpose of 
> implementing concurrency.
> Crafting high-performance, readable concurrency code can be difficult and 
> inconvenient to write.
> Swift already derives conformance to a number of protocols, automatically 
> synthesizing their inner-workings when possible. Since there is precedent for 
> synthesized conformances in Swift, we propose extending it to concurrency in 
> predictable circumstances.
> 
> Proposed solution
> In general, we propose that a type synthesize conformance to Concurrent as 
> long as the compiler has reasonable insight into the type. We describe the 
> specific conditions under which these conformances are synthesized below, 
> followed by the details of how the conformance requirements are implemented.
> 
> Requesting synthesis is opt-in
> Users must opt-in to automatic synthesis by declaring their type as 
> Concurrent without implementing any of its requirements. This conformance 
> must be part of the original type declaration and not on an extension (see 
> Synthesis in extensions below for more on this).
> Any type that declares such conformance and satisfies the conditions below 
> will cause the compiler to synthesize an implementation of an internalQueue 
> and async or sync for all properties and methods on that type.
> Making the synthesis opt-in—as opposed to automatic derivation without an 
> explicit declaration—provides a number of benefits:
> The syntax for opting in is natural; there is no clear analogue in Swift 
> today for having a type opt out of a feature.
> It requires users to make a conscious decision about the public API surfaced 
> by their types. Types cannot accidentally "fall into" conformances that the 
> user does not wish them to; a type that does not initially support Concurrent 
> can be made to at a later date, but the reverse is a potentially breaking 
> change.
> The conformances supported by a type can be clearly seen by examining its 
> source code; nothing is hidden from the user.
> We reduce the work done by the compiler and the amount of code generated by 
> not synthesizing conformances that are not desired and not used.
> As will be discussed later, explicit conformance significantly simplifies the 
> implementation for recursive types.
> 
> Overriding synthesized conformances
> Any user-provided implementations of an internalQueue and use of async or 
> sync will override the default implementations that would be provided by the 
> compiler.
> 
> Defining conditions where synthesis is allowed
> For example take the struct below, which contains all-kinds of properties; 
> variable, constant, and computed.
> struct Person {
>     var name: String // Variable Property
>     let birthday: Date // Constant Property
>     var age: Int { // Computed Property
>         /* - */
>     }
> }
> 
> Synthesized Requirements
> 
> Constant Properties
> Constants are always accessed asynchronously.
> A Constant is guaranteed to be immutable and therefore able to be read from 
> any thread without concern for unexpected mutation.
> The compiler sees this Constant as storage for a value.
> 
> // Compiler View - of a Constant Property
> struct Person {
>     /* Variable Property */
> 
>     // Constant Property
>     let birthday: Date {
>         get {
>             return underlying_Birthday_Date_Value_Storage
>         }
>     }
> 
>     /* Computed Property */
> }
> 
> After opting-in to the Concurrent protocol, the compiler synthesizes this 
> implementation, adding an asynchronous access point to any Constant on a 
> Concurrent type.
> 
> // Compiler View - of a Constant Property on a Concurrent type
> struct Person: Concurrent {
>     /* Variable Property */
> 
>     // Constant Property
>     let birthday: Date {
>         get {
>             internalQueue.async { // Immediately returns on calling thread
>                 return underlying_Birthday_Date_Value_Storage
>             }
>         }
>     }
> 
>     /* Computed Property */
> }
> 
> Synthesized requirements for Variable Properties
> Variables are always accessed synchronously.
> A Variable is mutable and therefore each thread must schedule writes and 
> reads separately out of concern for possible mutation.
> Just like a Constant, the compiler sees this Variable as storage for a value.
> 
> // Compiler View - of a Variable Property
> struct Person {
>     // Variable Property
>     var name: String {
>         get {
>             return underlying_Name_String_Value_Storage
>         }
>         set (newValue) {
>             underlying_Name_String_Value_Storage = newValue
>         }
>     }
>     
>     /* Constant Property */
>     /* Computed Property */
> }
> 
> Here the compiler synthesizes synchronous access (read or write) to any 
> Variable on a Concurrent type.
> 
> // Compiler View - of a Variable Property on a Concurrent type
> struct Person: Concurrent {
>     // Variable Property
>     var name: String {
>         get {
>             internalQueue.sync { // Wait to ensure all mutation has finished
>                 return underlying_Name_String_Value_Storage
>             }
>         }
>         set (newValue) {
>             internalQueue.sync { // Schedule this mutation to happen, in order
>                 underlying_Name_String_Value_Storage = newValue
>             }
>         }
>     }
> 
>     /* Constant Property */
>     /* Computed Property */
> }
> 
> Synthesized requirements for Computed Properties
> Computed Properties are always accessed synchronously.
> A Computed Property is essentially a function that gets called to create a 
> value from other values. These other values can be mutable and therefore each 
> thread must schedule writes and reads separately out of concern for possible 
> mutation. (Note: If a computed property only accesses Constants, it should 
> probably be a one-time set Constant; Swift <https://github.com/apple/swift> 
> could use a few proposals in this area.)
> 
> // Compiler View - of a Variable Property
> struct Person {
>     /* Variable Property */
>     /* Constant Property */
> 
>     // Computed Property
>     var age: Int {
>         // Compute age from birthday; return
>     }
> }
> 
> Here the compiler synthesizes synchronous access (read or write) to any 
> Computed Property on a Concurrent type.
> 
> // Compiler View - of a Variable Property on a Concurrent type
> struct Person: Concurrent {
>     /* Variable Property */
>     /* Constant Property */
> 
>     // Computed Property
>     var age: Int {
>         internalQueue.sync { // Wait to ensure all mutation has finished
>             // Compute age from birthday; return
>         }
>     }
> }
> 
> Considerations for recursive types and abstraction
> By making the synthesized conformances opt-in, recursive types have their 
> requirements fall into place with no extra effort. In any cycle belonging to 
> a recursive type, every type in that cycle must declare its conformance 
> explicitly. If a type does so but cannot have its conformance synthesized 
> because it does not satisfy the conditions above, then it is simply an error 
> for that type and not something that must be detected earlier by the compiler 
> in order to reason about all the other types involved in the cycle. (On the 
> other hand, if conformance were implicit, the compiler would have to fully 
> traverse the entire cycle to determine eligibility, which would make 
> implementation much more complex).
> With respect to abstraction, the idea that a synchronous function or property 
> can access another synchronous function or property, introduces a problem: 
> Deadlocking.
> 
> The Deadlock Problem
> Or just Deadlocking, is a problem where a complex program cannot continue 
> execution because one or more threads is waiting on a resource to become 
> available or for another task to complete.
> Anytime a synchronous function or property accesses another synchronous 
> function or property; this is defined as a Deadlock, because the first cannot 
> finish without the second being run and the second cannot execute without the 
> first being finished.
> 
> Solving the Deadlock Problem
> In complex functions where any number of synchronous and asynchronous calls 
> can happen inside a larger scope it is required that the compiler know how to 
> handle compilation of such functions, that may access many concurrent objects 
> through a multitude of calls. Much like Automatic Reference Counting 
> <https://en.wikipedia.org/wiki/Automatic_Reference_Counting> increments and 
> decrements a counter to determine whether an object should be marked for 
> deallocation, we suggest that during compilation a call or set of calls is 
> handled by evaluating their concurrent requirements.
> I.e. When a call nests as such:
> 
> // Compiler View - of a Complex Function on a Concurrent type
> 
> func heavyLift() {
>     syncFunction() // 1 Sync
>     
>     async() // 1 Async
>     
>     syncSomeFunction() // 2 Sync
>     syncSomeOtherFunction() // 3 Sync
>     
>     asyncSomeFunction() // 2 Async
>     asyncSomeOtherFunction() // 3 Async
> }
> 
> The compiler should implement a non-modified function, exactly as it would 
> today, and wrap each usage in-scope with an asynchronous or synchronous 
> requirement.
> Specifically:
> If a higher-level function accesses only asynchronous functions or properties 
> internally, that function can be executed in-order as a single asynchronous 
> call and inlining access to all non-modified calls.
> The same is true of synchronous functions or properties. They can be executed 
> in-order as a single synchronous call and inlining access to existing 
> non-modified calls.
> If at any point a function or property, accesses an asynchronous and 
> synchronous call then that function must be run as a single synchronous call.
> 
> Implementation details
> Deadlock Prevention is then inherent by synthesis. The following example 
> explains this through a chunk of modified, disassembled Swift code.
> 
> // Disassembly View - of an Integer Assignment without Thread-Safety
> int __T07Project14ConcurrentTypeC21functionWithoutSafetyySi5value_tF(int 
> arg0) { // Standard
>     _swift_beginAccess(__T07Project6objectSiv, &var_30, 0x1, 0x0);
>     *__T07Project6objectSiv = arg0;
>     rax = _swift_endAccess(&var_30);
>     return rax;
> }
> 
> // Disassembly View - of an Integer Assignment with Asynchronous Access
> int __T07Project14ConcurrentTypeC17functionWithAsyncySi5value_tF(int arg0) { 
> // AsyncCall
>     _swift_beginAccess(r13 + 0x10, &var_30, 0x0, 0x0);
>     _swift_endAccess(&var_30, &var_30, 0x0, 0x0);
>     rax = _swift_rt_swift_allocObject();
>     
>     // Call the Standard Function
>     __T07Project14ConcurrentTypeC21functionWithoutSafetyySi5value_tF(int arg0)
>     
>     var_60 = __NSConcreteStackBlock;
>     var_98 = _Block_copy(&var_60);
>     var_A0 = 
> __T0So13DispatchQueueC0A0E5asyncySo0A5GroupCSg5group_AC0A3QoSV3qosAC0A13WorkItemFlagsV5flagsyyXB7executetFfA_(&var_60,
>  0x18, __NSConcreteStackBlock);
>     var_A1 = 
> __T0So13DispatchQueueC0A0E5asyncySo0A5GroupCSg5group_AC0A3QoSV3qosAC0A13WorkItemFlagsV5flagsyyXB7executetFfA0_();
>     
> __T0So13DispatchQueueC0A0E5asyncySo0A5GroupCSg5group_AC0A3QoSV3qosAC0A13WorkItemFlagsV5flagsyyXB7executetF(var_A0,
>  var_A1 & 0xff, __NSConcreteStackBlock, 
> __T0So13DispatchQueueC0A0E5asyncySo0A5GroupCSg5group_AC0A3QoSV3qosAC0A13WorkItemFlagsV5flagsyyXB7executetFfA1_(&var_60,
>  0x18), var_98);
>     rax = _swift_rt_swift_release(rax, var_A1 & 0xff);
>     return rax;
> }
> 
> // Disassembly View - of an Integer Assignment with Synchronous Access
> int __T07Project14ConcurrentTypeC16functionWithSyncySi5value_tF(int arg0) { 
> // SyncCall
>     _swift_beginAccess(r13 + 0x10, &var_28, 0x0, 0x0);
>     _swift_endAccess(&var_28, &var_28, 0x0, &var_28);
>     rax = _swift_rt_swift_allocObject();
>     
>     // Call the Standard Function
>     __T07Project14ConcurrentTypeC21functionWithoutSafetyySi5value_tF(int arg0)
>     
>     var_58 = __NSConcreteStackBlock;
>     var_90 = _Block_copy(&var_58);
>     _swift_rt_swift_release(rax, 0x18);
>     dispatch_sync(*(r13 + 0x10), var_90);
>     rax = _Block_release(var_90);
>     return rax;
> }
> 
> Here are 3 functions, one which assigns a value to an integer without any 
> safety, like a non-concurrent type. As well as two more which call that 
> function using asynchronous and synchronous access respectively.
> The compiler determines which access should be used in a given scope, and 
> places that scope inside a synchronous or asynchronous call.
> Take a function which assigns this integer twice asynchronously:
> 
>     // Standard Call
>     +---------------------------------------+
>     | TwoAsyncs                             |
>     |    +-------------+   +-------------+  |
>     |    |  AsyncCall  |   |  AsyncCall  |  |
>     |    +-------------+   +-------------+  |
>     +---------------------------------------+
> One might reason that this function would fire-and-forget those calls, but 
> instead the compiler is rectifying them as a single async.
> 
>     // Single Asynchronous Call
>     +---------------------------------------+
>     | TwoAsyncs (Actually)                  |
>     |    +-------------+   +-------------+  |
>     |    |  Standard   |   |   Standard  |  |
>     |    +-------------+   +-------------+  |
>     +---------------------------------------+
> This behavior is the same for synchronous-only functions; however, instead of 
> rehashing lets look at the more interesting complex case. We start with this:
> 
>     // Standard Call
>     +---------------------------------------+
>     | AnyAsync/SyncCombination              |
>     |    +-------------+   +-------------+  |
>     |    |  AsyncCall  |   |   SyncCall  |  |
>     |    +-------------+   +-------------+  |
>     +---------------------------------------+
> But in actuality the compiler has composed a single synchronous call, since 
> there is a sync call at any point in the function.
> 
>     // Single Synchronous Call
>     +---------------------------------------+
>     | AnyAsync/SyncCombination (Actually)   |
>     |    +-------------+   +-------------+  |
>     |    |  Standard   |   |   Standard  |  |
>     |    +-------------+   +-------------+  |
>     +---------------------------------------+
> Let's do one more for added clarity.
>     
> +-------------------------------------------------------------------------------------+
>     | Combination                                                             
>             |
>     | +---------------------------------------+ 
> +---------------------------------------+ |
>     | | TwoAsyncs                             | | AnyAsync/SyncCombination    
>           | |
>     | |    +-------------+   +-------------+  | |    +-------------+   
> +-------------+  | |
>     | |    |  AsyncCall  |   |  AsyncCall  |  | |    |  AsyncCall  |   |   
> SyncCall  |  | |
>     | |    +-------------+   +-------------+  | |    +-------------+   
> +-------------+  | |
>     | +---------------------------------------+ 
> +---------------------------------------+ |
>     
> +-------------------------------------------------------------------------------------+
> Becomes:
>     
> +-------------------------------------------------------------------------------------+
>     | Combination //Sync                                                      
>             |
>     | +---------------------------------------+ 
> +---------------------------------------+ |
>     | | TwoAsyncs //Async                     | | AnyAsync/SyncCombination 
> //Sync       | |
>     | |    +-------------+   +-------------+  | |    +-------------+   
> +-------------+  | |
>     | |    |  Standard   |   |   Standard  |  | |    |  Standard   |   |   
> Standard  |  | |
>     | |    +-------------+   +-------------+  | |    +-------------+   
> +-------------+  | |
>     | +---------------------------------------+ 
> +---------------------------------------+ |
>     
> +-------------------------------------------------------------------------------------+
> 
> We've already made great decisions about thread-safety by implementing 
> SE-0035 
> <https://github.com/apple/swift-evolution/blob/master/proposals/0035-limit-inout-capture.md>
>  for Value Types
> SE-0035 Limiting Inout Capture 
> <https://github.com/apple/swift-evolution/blob/master/proposals/0035-limit-inout-capture.md>
>  actually provides us with a proof-of-concept as to why Value Types should 
> not be asynchronously mutated inside a closure.
> 
> Source compatibility
> By making the conformance opt-in, this is a purely additive change that 
> should not affect existing code and should be easily applicable to stdlib 
> types.
> Some current types using Grand Central Dispatch 
> <https://github.com/apple/swift-corelibs-libdispatch> should be audited for 
> recursive-implementation, if a user wishes to replace their own 
> implementation with this synthesized one.
> 
> Effect on ABI stability
> This feature is purely additive and should not change ABI.
> (Additionally, see Explicit Manglings for Sync/Async below for more on this.)
> 
> Effect on API resilience
> N/A.
> 
> Alternatives considered
> In order to realistically scope this proposal, we considered but ultimately 
> deferred the following items, some of which could be proposed additively in 
> the future.
> 
> Synthesis in extensions
> Requirements will be synthesized only for protocol conformances that are part 
> of the type declaration itself; conformances added in extensions will not be 
> synthesized.
> However, to align with Codable in the context of SR-4920 
> <https://bugs.swift.org/browse/SR-4920>, we will also currently forbid 
> synthesized requirements in extensions in the same file; this specific case 
> can be revisited later for all derived conformances.
> 
> Explicit Manglings
> Because accesses are compiled and their async or sync wrappers are 
> deterministic from use case, it may be useful to create specific Manglings; 
> this is optional.
> 
> Embedding or Building Dispatch
> Dispatch already provides us with a very powerful and exacting standard for 
> concurrency in Swift, it would be even more useful if embedded directly in 
> the runtime with replacements like that currently in the Runtime 
> <https://github.com/apple/swift/blob/master/stdlib/public/runtime/Once.cpp#L31>.
> I feel as if most of the reason this would be frowned upon is a Swift desire 
> for style, code cleanliness and some hope of a 'better' (whatever that means 
> to you) solution.
> Yet, we could add the existing library to the Runtime or even rewrite Grand 
> Central Dispatch <https://github.com/apple/swift-corelibs-libdispatch> as a 
> Swift project and embed it in the Standard Library.
> Ideally, this would be deferred to a separate Swift Evolution Proposal.
> 
> Keyword Overrides
> It is worth mentioning a Keyword could be used for overriding a function and 
> defining explicit behavior as sync or async. However, this opens the door to 
> misuse and incorrect code that can only be debugged at runtime with TSAN. And 
> while we love TSAN:
>       TSAN how I love thee. Let me non atomically count the ways…
>                                                               - Philippe 
> Hausler
> This is not a good idea.
> 
> Acknowledgments
> Thanks to everyone in the Swift Community working to make it an even more 
> vibrant place. And especially to those who worked on SE-0166 
> <https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md>
>  and SE-0185 
> <https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md>.
>  Whom might notice large parts of a shared ideal, that made this proposal 
> much easier to write.
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to