Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-9662] Subclassing an Objective-C class that returns a block unexpectedly (?) emits an unnecessary reabstraction thunk #52106

Open
liscio opened this issue Jan 14, 2019 · 2 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@liscio
Copy link

liscio commented Jan 14, 2019

Previous ID SR-9662
Radar rdar://47260348
Original Reporter @liscio
Type Bug

Attachment: Download

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: bcdbe249b340f43b9fe2c2887834b365

Issue Description:

I'm a long-time audio developer that would love to explore the use of Swift from inside a realtime audio context. Unfortunately, despite my best efforts to keep my Swift code free from allocations and other such verboten operations, I am blocked by the compiler inserting some unnecessary "danger" (in the form of swift_allocObject) into the mix…

It would appear that there is a mismatch somewhere—either in the compiler, or the API definition—that is forcing my returned @convention(swift) function to get wrapped into a @convention(block) function that invokes the unwanted object allocation.

I have also filed rdar://47260348 for the audio team at Apple, in case this can get resolved by a simple API annotation that declares @convention(block) for the return type of internalRenderBlock.

Anyway, the attached sample code demonstrates what I am talking about. It can be used as follows on a Mac w/ the Xcode 10.1 tools installed. Just launch Terminal, and run:

./compileIt.sh

That compiles the included swift file and runs the disassembled output through swift-demangle. Look for "reabstraction thunk helper"—that is the function that is invoked by the realtime audio thread instead of the "closure #1" function that you would hope is returned and invoked by the audio subsystem.

I don't know if this is an error in the API declaration, a mis-interpretation of the API by the Swift compiler, or a failure of the compiler to recognize that there is no need to wrap the returned block. I am way outside my element here, so I'll let you folks classify what's going wrong here.

@belkadan
Copy link
Contributor

As you note, the compiler doesn't provide any guarantees about things like this today. However, there's a reasonable optimization in here that we are missing: don't call _Block_copy for a block created from a closure known to have no captures. (Even Objective-C will do a heap allocation for a block that has captures; you can't get around it.)

cc @eeckstein

@liscio
Copy link
Author

liscio commented Jan 17, 2019

Just so we are on the same page, I expect the following conditions to hold:

  1. The caller of internalRenderBlock makes a copy of the block that I return.
  2. Any variables that I capture explicitly (see below) will get captured during the _Block_copy invoked by the caller of internalRenderBlock.
  3. The block that gets returned should be equivalent to whatever closure #​1 is emitted by internalRenderBlock.
  4. More specifically, when the audio system invokes the block that I return, the first instruction executed should be whatever code corresponds to the 1st line of the returned closure.

Regarding the capture: What I have in mind is something like this:

// The "math parts" of the audio unit
final class DSPKernel {
  // Assume this contains:
  //   * Statically-dispatched, thread-safe parameter updaters
  //   * A statically-dispatched buffer reader/writer function
}

final class MyAudioUnit: AUAudioUnit {
  var someDSPKernel: DSPKernel
  
  var internalRenderBlock: AUInternalRenderBlock {
    return { 
        [kernel = self.someDSPKernel] // Don't capture self
        (param1, param2, ...) in
      kernel.readAndWriteBuffers(param1, param2, ...)
    }
  }
}

Given my current understanding of Swift, the kernel capture above should act as any other pointer would be passed along with a block. Once the block is constructed, and returned in the getter above, I would expect:the reference is bound to the block object, and when the block is called there should be no further allocations (i.e. _swift_Alloc) required inside the closure #​1. (Provided, of course, that I take care not to allocate anything in readAndWriteBuffers and any code invoked within.)

Please let me know if I am missing (or appear to be misunderstanding) anything. Thanks!

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

2 participants