Skip to content

[stdlib] Simplify buffer pointer initialization. #34879

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

Merged
merged 1 commit into from
Dec 1, 2020

Conversation

Lukasa
Copy link
Contributor

@Lukasa Lukasa commented Nov 27, 2020

UnsafeRawBufferPointer was not made self-slicing, but is instead sliced
by Slice. When working with
UnsafeRawBufferPointer objects it is quite common to want to transform
back into the underlying collection, which is why the
UnsafeRawBufferPointer provides a constructor from its subsequence:
UnsafeRawBufferPointer.init(rebasing:).

Unfortunately for an initializer on this extremely low-level type, this
initializer is surprisingly expensive. When disassembled on Linux it
emits 7 separate traps and 11 branches. This relative heft means this
method often gets outlined, which is a shame, as several of the branches
could often be eliminated due to checks elsewhere in functions where
this initializer is used.

Almost all of these branches and almost all of the traps are
unnecessary. We can remove them by noting that it is impossible to
construct a Slice whose endIndex is earlier than its startIndex. All
Slice inits are constructed with bounds expressed as Range, and Range
enforces ordering on its endpoints.

For this reason, we can do unchecked arithmetic on the start and end
index of the slice, and remove 5 traps in one fell swoop. This greatly
cheapens the cost of the initializer, improving its odds of being
inlined and having even more of its branches optimised away.

For what it's worth, I also considered trying to remove the last two
preconditions. Unfortunately I concluded I couldn't confidently do that.
I wanted to remove them based on the premise that a valid Slice must
have indices that were in-bounds for its parent Collection, and so we
could safely assume that if the parent base address was nil the count
would have to be zero, and that adding start index to the base address
would definitely not overflow. However, the existence of the
Slice.init(base:bounds:) constructor led me to be a bit suspicions of
removing those checks. Given that
UnsafeRawBufferPointer.init(start:count:) enforces those invariants, I
decided it was safest for us to continue to do that.

@Lukasa
Copy link
Contributor Author

Lukasa commented Nov 27, 2020

@swift-ci test

@Lukasa
Copy link
Contributor Author

Lukasa commented Nov 27, 2020

CC @atrick and @gottesmm who are likely both interested here.

To the two of you: I can get rid of the last two preconditions, but doing so removes some guard rails. The two preconditions come from init(start:count:) and they are:

https://github.com/apple/swift/blob/887464b7b67d5202bfa7adc4e3f045ff1027a5a7/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb#L435-L437

The first precondition here is impossible, for the same reason we could do unchecked subtraction on startIndex and endIndex: Range is ordered. So we can also kill that branch and trap. However, the other one is a bit closer to the wire. Technically, I can lean on the requirement that a Slice is only valid if both startIndex and endIndex are valid for the base Collection, and that when the base address is nil the only valid values for startIndex and endIndex are 0. This already meets our precondition, so we can technically remove it.

However, users are capable of screwing this up because you can just whip up a Slice from anywhere, and unlike with Range it does not warn you about the danger of using Slice.init(base:bounds:). In general I think I'm comfortable with removing this precondition as well and making this function essentially trivial, but I wanted your opinion here.

Lukasa added a commit to Lukasa/swift-nio that referenced this pull request Nov 27, 2020
Motivation:

I recently discovered that UnsafeRawBufferPointer.init(rebasing:) is
surprisingly expensive, with 7 traps and 11 branches. A simple
replacement can make it a lot cheaper, down to two traps and four
branches. This ends up having pretty drastic effects on
ByteBuffer-heavy NIO code, which often outlines the call to that
initializer and loses the ability to make a bunch of site-local
optimisations.

While this has been potentially fixed upstream with
swiftlang/swift#34879, there is no good reason to
wait until Swift 5.4 for this improvement.

Due to the niche use-case, I didn't bother doing this for _every_
rebasing in the program. In particular, there is at least one
UnsafeBufferPointer(rebasing:) that I didn't do this with, and there are
uses in both NIOTLS and NIOHTTP1 that I didn't change. While we can fix
those if we really need to, it would be nice to avoid this helper
proliferating too far through our codebase.

Modifications:

- Replaced the use of URBP.init(rebasing:) with a custom hand-rolled
  version that avoids Slice.count.

Result:

Cheaper code. One NIOHTTP2 benchmark sees a 2.9% speedup from this
change alone.
@Lukasa
Copy link
Contributor Author

Lukasa commented Nov 27, 2020

Worked around in SwiftNIO at apple/swift-nio#1696.

UnsafeRawBufferPointer was not made self-slicing, but is instead sliced
by Slice<UnsafeRawBufferPointer>. When working with
UnsafeRawBufferPointer objects it is quite common to want to transform
back into the underlying collection, which is why the
UnsafeRawBufferPointer provides a constructor from its subsequence:
UnsafeRawBufferPointer.init(rebasing:).

Unfortunately for an initializer on this extremely low-level type, this
initializer is surprisingly expensive. When disassembled on Linux it
emits 7 separate traps and 11 branches. This relative heft means this
method often gets outlined, which is a shame, as several of the branches
could often be eliminated due to checks elsewhere in functions where
this initializer is used.

Almost all of these branches and almost all of the traps are
unnecessary. We can remove them by noting that it is impossible to
construct a Slice whose endIndex is earlier than its startIndex. All
Slice inits are constructed with bounds expressed as Range, and Range
enforces ordering on its endpoints.

For this reason, we can do unchecked arithmetic on the start and end
index of the slice, and remove 5 traps in one fell swoop. This greatly
cheapens the cost of the initializer, improving its odds of being
inlined and having even more of its branches optimised away.

For what it's worth, I also considered trying to remove the last two
preconditions. Unfortunately I concluded I couldn't confidently do that.
I wanted to remove them based on the premise that a valid Slice must
have indices that were in-bounds for its parent Collection, and so we
could safely assume that if the parent base address was nil the count
would have to be zero, and that adding start index to the base address
would definitely not overflow. However, the existence of the
Slice.init(base:bounds:) constructor led me to be a bit suspicions of
removing those checks. Given that
UnsafeRawBufferPointer.init(start:count:) enforces those invariants, I
decided it was safest for us to continue to do that.
@Lukasa Lukasa force-pushed the cb-simpler-buffer-initializer branch from ed1d406 to 300f4da Compare November 27, 2020 16:58
@Lukasa
Copy link
Contributor Author

Lukasa commented Nov 27, 2020

@swift-ci test

@karwa
Copy link
Contributor

karwa commented Nov 27, 2020

I can get rid of the last two preconditions, but doing so removes some guard rails. The two preconditions come from init(start:count:) and they are:

I'm trying to remove these in #34747 but it caused some benchmark regressions (e.g. SuffixAnySequence, +200% at -Osize is probably not noise). I haven't had a chance to investigate those in detail, but my hunch is that they allow other preconditions/overflow checks to be omitted later on. I don't think we have a way to tell the compiler "assume this statement is true, but don't trap if it isn't" (well... I kind of discovered a way, but it's awful).

@Lukasa
Copy link
Contributor Author

Lukasa commented Nov 28, 2020

I'm trying to remove these in #34747 but it caused some benchmark regressions (e.g. SuffixAnySequence, +200% at -Osize is probably not noise). I haven't had a chance to investigate those in detail, but my hunch is that they allow other preconditions/overflow checks to be omitted later on. I don't think we have a way to tell the compiler "assume this statement is true, but don't trap if it isn't" (well... I kind of discovered a way, but it's awful).

I’m not proposing to remove them in full generality, just in the specific constructor from Slice.

@Lukasa
Copy link
Contributor Author

Lukasa commented Nov 28, 2020

@swift-ci benchmark

@swift-ci
Copy link
Contributor

Performance: -O

Regression OLD NEW DELTA RATIO
CharIteration_tweet_unicodeScalars 3720 4840 +30.1% 0.77x
CharIteration_ascii_unicodeScalars 1880 2440 +29.8% 0.77x (?)
CharIteration_punctuated_unicodeScalars 480 560 +16.7% 0.86x
CharIteration_punctuatedJapanese_unicodeScalars 520 560 +7.7% 0.93x (?)
DropFirstArrayLazy 13 14 +7.7% 0.93x (?)
 
Improvement OLD NEW DELTA RATIO
EqualSubstringSubstring 30 21 -30.0% 1.43x
EqualSubstringString 30 21 -30.0% 1.43x
LessSubstringSubstringGenericComparable 30 21 -30.0% 1.43x
LessSubstringSubstring 29 21 -27.6% 1.38x
EqualSubstringSubstringGenericEquatable 29 21 -27.6% 1.38x
EqualStringSubstring 30 22 -26.7% 1.36x
CharIndexing_punctuated_unicodeScalars_Backwards 880 760 -13.6% 1.16x
ObjectiveCBridgeStringHash 66 61 -7.6% 1.08x (?)
PrefixArray 14 13 -7.1% 1.08x (?)
StringComparison_abnormal 580 540 -6.9% 1.07x (?)

Code size: -O

Regression OLD NEW DELTA RATIO
RomanNumbers.o 5404 5644 +4.4% 0.96x
ReduceInto.o 9787 10107 +3.3% 0.97x

Performance: -Osize

Regression OLD NEW DELTA RATIO
SequenceAlgosArray 1960 2400 +22.4% 0.82x
IterateData 803 899 +12.0% 0.89x (?)
SortAdjacentIntPyramids 940 1025 +9.0% 0.92x (?)
PrefixArrayLazy 13 14 +7.7% 0.93x (?)
 
Improvement OLD NEW DELTA RATIO
EqualSubstringSubstring 29 21 -27.6% 1.38x
EqualSubstringSubstringGenericEquatable 29 21 -27.6% 1.38x
EqualSubstringString 29 21 -27.6% 1.38x
LessSubstringSubstring 30 22 -26.7% 1.36x
EqualStringSubstring 30 22 -26.7% 1.36x
LessSubstringSubstringGenericComparable 30 22 -26.7% 1.36x
FlattenListFlatMap 3697 2838 -23.2% 1.30x (?)
CharIteration_russian_unicodeScalars 2680 2160 -19.4% 1.24x
CharIteration_ascii_unicodeScalars 2320 1880 -19.0% 1.23x
CharIteration_tweet_unicodeScalars 4560 3720 -18.4% 1.23x
CharIndexing_tweet_unicodeScalars 7920 6520 -17.7% 1.21x (?)
CharIteration_japanese_unicodeScalars 3720 3080 -17.2% 1.21x (?)
CharIndexing_ascii_unicodeScalars 4000 3320 -17.0% 1.20x
CharIteration_chinese_unicodeScalars 2240 1880 -16.1% 1.19x
CharIteration_korean_unicodeScalars 2760 2320 -15.9% 1.19x
CharIteration_punctuatedJapanese_unicodeScalars 520 440 -15.4% 1.18x
CharIteration_punctuated_unicodeScalars 520 440 -15.4% 1.18x
CharIndexing_punctuated_unicodeScalars 880 760 -13.6% 1.16x (?)
StringComparison_longSharedPrefix 362 327 -9.7% 1.11x (?)
CharIndexing_korean_unicodeScalars 4680 4240 -9.4% 1.10x (?)
WordCountHistogramUTF16 3300 3000 -9.1% 1.10x (?)
CharIteration_utf16_unicodeScalars 3080 2800 -9.1% 1.10x (?)
CharIndexing_utf16_unicodeScalars 5040 4680 -7.1% 1.08x (?)

Code size: -Osize

Performance: -Onone

Regression OLD NEW DELTA RATIO
ArrayAppendLatin1Substring 29088 32220 +10.8% 0.90x (?)
ArrayAppendAsciiSubstring 28656 31716 +10.7% 0.90x (?)
ArrayAppendUTF16Substring 28692 31752 +10.7% 0.90x (?)
 
Improvement OLD NEW DELTA RATIO
StringBuilderWithLongSubstring 2910 2670 -8.2% 1.09x (?)
EqualSubstringSubstringGenericEquatable 87 80 -8.0% 1.09x (?)
LessSubstringSubstringGenericComparable 86 80 -7.0% 1.07x (?)
LessSubstringSubstring 87 81 -6.9% 1.07x (?)
EqualSubstringSubstring 88 82 -6.8% 1.07x (?)

Code size: -swiftlibs

How to read the data The tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.

If you see any unexpected regressions, you should consider fixing the
regressions before you merge the PR.

Noise: Sometimes the performance results (not code size!) contain false
alarms. Unexpected regressions which are marked with '(?)' are probably noise.
If you see regressions which you cannot explain you can try to run the
benchmarks again. If regressions still show up, please consult with the
performance team (@eeckstein).

Hardware Overview
  Model Name: Mac mini
  Model Identifier: Macmini8,1
  Processor Name: 6-Core Intel Core i7
  Processor Speed: 3.2 GHz
  Number of Processors: 1
  Total Number of Cores: 6
  L2 Cache (per Core): 256 KB
  L3 Cache: 12 MB
  Memory: 64 GB

@Lukasa
Copy link
Contributor Author

Lukasa commented Nov 30, 2020

@swift-ci test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 300f4da

@Lukasa
Copy link
Contributor Author

Lukasa commented Nov 30, 2020

@swift-ci test

glbrntt pushed a commit to apple/swift-nio that referenced this pull request Nov 30, 2020
Motivation:

I recently discovered that UnsafeRawBufferPointer.init(rebasing:) is
surprisingly expensive, with 7 traps and 11 branches. A simple
replacement can make it a lot cheaper, down to two traps and four
branches. This ends up having pretty drastic effects on
ByteBuffer-heavy NIO code, which often outlines the call to that
initializer and loses the ability to make a bunch of site-local
optimisations.

While this has been potentially fixed upstream with
swiftlang/swift#34879, there is no good reason to
wait until Swift 5.4 for this improvement.

Due to the niche use-case, I didn't bother doing this for _every_
rebasing in the program. In particular, there is at least one
UnsafeBufferPointer(rebasing:) that I didn't do this with, and there are
uses in both NIOTLS and NIOHTTP1 that I didn't change. While we can fix
those if we really need to, it would be nice to avoid this helper
proliferating too far through our codebase.

Modifications:

- Replaced the use of URBP.init(rebasing:) with a custom hand-rolled
  version that avoids Slice.count.

Result:

Cheaper code. One NIOHTTP2 benchmark sees a 2.9% speedup from this
change alone.
Copy link
Contributor

@ktoso ktoso left a comment

Choose a reason for hiding this comment

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

Fantastic stuff, Cory 👍

Copy link
Contributor

@atrick atrick left a comment

Choose a reason for hiding this comment

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

This PR itself is great. Thanks a lot! I'll add some comments to the discussion about the other possibilities.

@atrick atrick merged commit 820b2c6 into swiftlang:main Dec 1, 2020
@atrick
Copy link
Contributor

atrick commented Dec 1, 2020

@Lukasa @lorentey I'm going to invent some rationale for when we need preconditions, so I'd like to hear what you think about it too.

I'll claim that we want preconditions the check consistency across user-provided arguments whenever we construct a value, regardless of whether the value's type is Unsafe.

For example, UBP.init(start:count:) should always check count >= 0 and non-nil start for count > 0.

When it comes to accessing that type, we may demote the argument check to a debugPrecondition, as in UBP.subscript().

On the other hand, when constructing Unsafe types, we can assume that any single argument value is already self-consistent. So, UBP.init(rebasing:) does not need to re-check the validity of Slice. After all, constructing an invalid Slice requires using Range.init(uncheckedBounds:).

So what could we do better:

  • As Cory suggests, we could eliminate the remaining checks from UBP.init(rebasing:) by bypassing the checked UBP.init(start:count:). I think that would be fine.

  • It seems like there's a bigger opportunity to optimize Slice.count here. Can we assume Slice is well-formed in general? Since it's not an Unsafe type, we can't really justify removing preconditions. But don't those preconditions belong on Slice construction, not access to the count? After all, users already implictly assume the start/endIndex properties are valid when they are directly accessed.

Are we confident enough that users won't misuse Slice.init(uncheckedBounds:) to do this? I did notice that Range.init(from decoder:) does not have any preconditions, which seems a little alarming.

@Lukasa
Copy link
Contributor Author

Lukasa commented Dec 2, 2020

On the other hand, when constructing Unsafe types, we can assume that any single argument value is already self-consistent. So, UBP.init(rebasing:) does not need to re-check the validity of Slice. After all, constructing an invalid Slice requires using Range.init(uncheckedBounds:)

Disappointingly I think this isn’t true. Slice has this constructor:
https://github.com/apple/swift/blob/b13a8e9ba3b4a1ae77e72f93813644a19206fc9f/stdlib/public/core/Slice.swift#L109-L114

This constructor allows an arbitrary Range and base instance to be provided for Slicing. While this constructor is primarily intended for users conforming to Collection, in principle it can be used arbitrarily. Because it does not validate that the bounds are valid for the base collection, nothing prevents users doing this:

let base = UnsafeRawBufferPointer(start: nil, count: 0)  // Well formed
let slice = Slice(base: base, bounds: 5..<10)  // Well-formed range, ill-formed slice, no precondition
let newBase = UnsafeRawBufferPointer(rebasing: slice)  // ???

I think it’s totally fine to assume that Range is well-formed, because the type enforces the constraint unless the user volunteers to opt-out. I think it’s less obviously fine to assume that Slice is well-formed, and so you could argue that we need to tolerate the possibility it isn’t. The question is, does that go so far as to bounds check the Slice provided? If we don’t feel we have to bounds check that Slice, do we have to check that the base pointer is nil? Why are these cases different?

@lorentey
Copy link
Member

lorentey commented Dec 3, 2020

I think the theory with Slice was that the underlying collection will perform index validation on every access anyway, so there is no need to perform any checking in the slice itself. (Beyond _failEarlyRangeCheck, which is a half-baked, underdocumented, non-public best-effort thing.)

Supposing the following statements are true:

  • Unsafe[Mutable][Raw]BufferPointer intentionally does not perform any index validation in optimized builds.
  • Passing a slice is intended to be semantically equivalent to passing the base collection along with a range of indices, with no guarantees whatsoever about how that range may or may not be valid in the collection.

Then I think it's reasonable to say that in release builds, constructing a buffer pointer from a slice must do exactly as many checks as constructing one through init(start:count:) -- which is none whatsoever. (Edit: Except for the count >= 0 check, which is in this case obviated by the use of Range, and the non-nil check, which can only be elided in a new initializer that takes a Range<UnsafePointer> instead of a start/count.)

(That said, we should definitely have _debugPreconditions in the rebasing initializers to help people catch issues.)

If we had a time machine, I would go back to the evolution discussion & I would be arguing loudly to make these buffer types self-slicing, with pointers as their index type. Unfortunately we don't have one, so we are stuck with a one-size-fits-all SubSequence implementation on a very unusual collection type.

@lorentey
Copy link
Member

lorentey commented Dec 3, 2020

Are we confident enough that users won't misuse SliceRange.init(uncheckedBounds:) to do this?

Yes, definitely! But init.(uncheckedBounds:) is currently missing a debugPrecondition, which is rather embarrassing. (I submitted #34951 to fix this.)

I did notice that Range.init(from decoder:) does not have any preconditions, which seems a little alarming.

That is very alarming. It should signal invalid data by throwing an error.

Edit: This is a false alarm -- Range.init(from:) does correctly throw an error when lowerBound > upperBound.

@atrick
Copy link
Contributor

atrick commented Dec 4, 2020

Then I think it's reasonable to say that in release builds, constructing a buffer pointer from a slice must do exactly as many checks as constructing one through init(start:count:) -- which is none whatsoever. (Edit: Except for the count >= 0 check, which is in this case obviated by the use of Range, and the non-nil check, which can only be elided in a new initializer that takes a Range<UnsafePointer> instead of a start/count.)

Right, I was suggesting that UBP.init(rebasing:) should not need the count >= 0 check or non-nil check, so it should be free.

@atrick
Copy link
Contributor

atrick commented Dec 4, 2020

@lorentey URBP was originally self-slicing (and UBP must have been too), before DaveA pointed out that generic algorithms broke because of non-transferable indices. Yeah, it's really unfortunate they don't have pointer indices, and I don't know what the historical reason for that was.

@atrick
Copy link
Contributor

atrick commented Dec 4, 2020

@Lukasa @lorentey I still don't understand why Slice.count needs any checks. Presumably it has an overflow check because the default Container.count uses distance?

@lorentey
Copy link
Member

lorentey commented Dec 4, 2020

Yeah -- Slice.count gets the default count, which dispatches to distance, which in UMBP's case reduces to a subtraction:

  @inlinable // unsafe-performance
  public func distance(from start: Int, to end: Int) -> Int {
    // NOTE: this is a manual specialization of index movement for a Strideable
    // index that is required for UnsafeBufferPointer performance. The
    // optimizer is not capable of creating partial specializations yet.
    // NOTE: Range checks are not performed here, because it is done later by
    // the subscript function.
    return end - start
  }

Unfortunately, if we accept that calling Slice(base: foo, bounds: Int.min..<Int.max) is legal, then we cannot really get rid of the overflow check. (Not even in .init(rebasing:) 😭)

@lorentey
Copy link
Member

lorentey commented Dec 4, 2020

We could replace - with subtractingReportingOverflow, though:

public func distance(from start: Int, to end: Int) -> Int {
  let result = end.subtractingReportingOverflow(start)
  _debugPrecondition(!result.overflow)
  return result.partialValue
}

(On the theory that an overflow here can only ever occur if start is negative, in which case the code has arguably already triggered undefined unspecified behavior by operating on an invalid slice.)

@atrick
Copy link
Contributor

atrick commented Dec 4, 2020

Interesting... and annoying that a valid Range has an invalid distance

@lorentey
Copy link
Member

lorentey commented Dec 4, 2020

Hm, expanding on the legal theory above:

  1. Collection.distance(from:to:) is only defined to operate on indices that are valid in the collection value at the time distance is called. Crucially, there is no requirement for distance to diagnose invalid indices -- which is why it is okay for e.g. Array.distance(from:to:) not to perform bounds checking on its input. Checking is still preferable to no checking, but performance concerns may override this, and in the case of U[M]BP, they probably should.

    If start < end, then ideally base.distance(from: start, to: end) wouldn't return a negative value. But if start and end are invalid indices, then comparing them or calculating their distance are meaningless operations -- so it is probably okay for them to return inconsistent results (such as when the subtraction overflows in distance).

  2. Slice evidently does not guarantee that its startIndex and endIndex are valid indices, just that start <= end. We are technically allowed to call the .init(base:bounds:) initializer with indices that aren't valid in the base collection. However, if we do so, most operations on the resulting slice will not have well-defined results. (Except the primitive accessors base, startIndex and endIndex.) In particular, slice.count forwards to distance on the base collection, so it is evidently okay for it to return silly values:

    let slice = Slice<Range<Int>>(base: 0..<10, bounds: 1000 ..< 11000)
    slice.count // ⟹ 10000

    Returning a negative count would be especially silly. But I think it is technically allowed, as long as the indices are invalid.

  3. The Unsafe*BufferPointer types are special in that in release builds, their operations are allowed to invoke full-blown undefined behavior (complete with nasal demons) when they are misused. (Misuse includes subscripting with an out-of-bounds index, or trying to access an invalid memory location.) In debug builds, they must diagnose out-of-bounds access, but (for obvious reasons) they aren't required to detect issues with the state of the underlying memory.

So, if I understand correctly:

  • U[M]BP.distance(from:to:) does not need to perform any bounds checking, and it is allowed to silently overflow if given invalid indices. (At least in release builds.)
  • U[M]BP.Slice is allowed to hold out-of-bounds startIndex and/or endIndex values. It doesn't matter what count returns in these cases.
  • U[M]BP.init(rebasing:) is required to diagnose out-of-bounds slices in debug builds. (Otherwise the resulting buffer may allow obviously out-of-bounds accesses in future operations.) However, in release builds, the initializer is allowed to assume that the input slice contains valid indices.

So if all this is right, then we should probably change U*BP.distance(from:to:) to the subtractingReportingOverflow definition above and rewrite the rebasing initializers in this PR with versions that perform full-blown bounds checking in debug mode:

  public init(rebasing slice: Slice<UnsafeMutableBufferPointer<Element>>) {
    // We should probably not call _failEarlyRangeCheck here because range
    // construction would likely emit unnecessary checks.
    _debugPrecondition(
       slice.startIndex >= 0 && slice.endIndex <= slice.base.count, 
      "Invalid slice") 
    // Note: this assumes https://github.com/apple/swift/pull/34951
    // is eventually merged (which it should be). Otherwise this should
    // initialize members directly, with an explicit check against `start == nil && count != 0`.
    self.init(
      start: slice.base.baseAddress?.advanced(by: slice.startIndex), 
      count: slice.count)
   }

@Lukasa
Copy link
Contributor Author

Lukasa commented Dec 4, 2020

That sounds reasonable to me. I’ll throw together a patch to that effect today.

@Lukasa Lukasa deleted the cb-simpler-buffer-initializer branch December 4, 2020 08:27
@lorentey
Copy link
Member

lorentey commented Dec 4, 2020

I'm on it already 😉 -- I have a new PR integrating what we learned from #34747, #34879, and #34951 that is coming as soon as I finish fixing the test failures.

@Lukasa
Copy link
Contributor Author

Lukasa commented Dec 4, 2020

Ace, I love not doing work I don't have to!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants