Skip to content
New issue

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

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

Already on GitHub? # to your account

Unify wrapper paths #1423

Merged
merged 3 commits into from
Mar 12, 2020
Merged

Unify wrapper paths #1423

merged 3 commits into from
Mar 12, 2020

Conversation

compnerd
Copy link
Contributor

@compnerd compnerd commented Mar 1, 2020

Merge the block and non-blocking sys call wrappers

Motivation:

The two paths are nearly identical with the core difference being that blocking calls ignore EWOULDBLOCK. This simplifies the handling for the different types by adding a parameter to the wrapSyscall method.

Modifications:

Added a new parameter nonblocking to the wrapSyscall and wrapSyscallWouldBlock. Those paths are then merged into a single call method.

Result:

No external changes.

Copy link
Member

@weissi weissi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with this if we can show that it leads to the same assembly. Back when we first wrote this, the optimiser couldn't optimise those two cases into the best code which is why we had to split it. I'm happy to undo this now if we can show that the code isn't affected by this.

if err == EINTR {
continue
}
if err == EINTR { continue }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we usually put newlines after every {

}

/* Sorry, we really try hard to not use underscored attributes. In this case however we seem to break the inlining threshold which makes a system call take twice the time, ie. we need this exception. */
@inline(__always)
@discardableResult
internal func wrapSyscall<T: FixedWidthInteger>(where function: String = #function, _ body: () throws -> T) throws -> T {
internal func call<T: FixedWidthInteger>(nonblocking: Bool, where function: String = #function, _ body: () throws -> T) throws -> IOResult<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still name this wrapSyscall or callSyscall or something. This wrapper only works if the called function behaves like a unix syscall (ie. -1 == error)

@compnerd
Copy link
Contributor Author

compnerd commented Mar 4, 2020

I think addressing the generated code issue first is more important, fixing the style stuff can come later. I checked on an ubuntu build, it depends on how you write the code and what you want the result to be.

Looking at the code for:

while true {
  let res = try body()
  if res == -1 {
    let err = errno
    if err == EINTR { continue }
    if blocking && err == EWOULDBLOCK { return .wouldBlock(0) }
    preconditionIsNotBlacklistedErrno(err: err, where: function)
    throw IOError(errnoCode: err, reason: function)
  }
  return .processed(res)
}

generates the same assembly (modulo offsets) - I've attached the before and after results.

However, it generates two functions, the static function and the merged function.

Writing it as:

while true {
  let res = try body()
  if res == -1 {
    let err = errno
    switch err {
    case EINTR: continue
    case EWOULDBLOCK:
      if blocking { return .wouldBlock(0) }
      fallthrough
    default:
      preconditionIsNotBlacklistedErrno(err: err, where: function)
      throw IOError(errnoCode: err, reason: function)
    }
  }
  return .processed(res)
}

produces a single function instead.

The single function is a little bit heavier on the frame size, but overall reduces the amount of code. There is a minor amount of extra overhead (2 extra cmp+jne, 4 total instructions) due to the unnecessary precondition check in the case of EWOULDBLOCK, but that is not going to happen anyways - and you can see that with -Ounchecked results.

@compnerd
Copy link
Contributor Author

compnerd commented Mar 4, 2020

The path was duplicated because the compiler was not previously able to
optimize with the constant value and would generate poor code.  This has
now been addressed, so unify the blocking and non-blocking paths.
@compnerd compnerd requested a review from weissi March 11, 2020 03:11
Copy link
Member

@weissi weissi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! That looks good to me

@weissi weissi requested a review from Lukasa March 12, 2020 10:53
@Lukasa Lukasa added the 🔨 semver/patch No public API change. label Mar 12, 2020
@Lukasa Lukasa added this to the 2.15.0 milestone Mar 12, 2020
@Lukasa
Copy link
Contributor

Lukasa commented Mar 12, 2020

@compnerd Are you still ok with us going ahead and merging this separate from #1440?

@compnerd
Copy link
Contributor Author

Yes, that would be wonderful. Small steps make it easier :)

@Lukasa Lukasa merged commit 648a671 into apple:master Mar 12, 2020
@compnerd compnerd deleted the wrapper-unity branch March 12, 2020 15:56
pull bot pushed a commit to scope-demo/swift-nio that referenced this pull request Mar 12, 2020
The path was duplicated because the compiler was not previously able to
optimize with the constant value and would generate poor code.  This has
now been addressed, so unify the blocking and non-blocking paths.

Co-authored-by: Cory Benfield <lukasa@apple.com>
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants