Upgrade to Pro — share decks privately, control downloads, hide ads and more …

掀起 SWIFT 的面紗

Pofat
October 20, 2018

掀起 SWIFT 的面紗

讓我們一起探討 Swift Standard Library 與一些基本型別、集合的幕後原理。

Talking about some basic type and collection in Swift Standard Library.

Pofat

October 20, 2018
Tweet

More Decks by Pofat

Other Decks in Programming

Transcript

  1. Clone 過 Android Source Code 嗎 讀過 Android Source Code

    嗎? 對 Android Source Code 有什什麼感想? 起源
  2. 如何開始 # Install cmake and ninja brew install cmake ninja

    mkdir swift-source cd swift-source # Clone Swift project to local git clone https://github.com/apple/swift.git ${ROOT} : swift-source here
  3. Compile! # Partially debug message ./swift/utils/build-script --release-debuginfo # Partially debug

    message ./swift/utils/build-script --debug # Partially debug message ./swift/utils/build-script --release
  4. 檔案架構 # build path (bin, gyb result, tests…) ${ROOT}/build/Ninja-RelWithDebInfoAssert/ swift-macosx-x86_64

    # executables ${ROOT}/build/Ninja-RelWithDebInfoAssert/ swift-macosx-x86_64/bin ${BUILD_ROOT} : ${ROOT}/build/Ninja-RelWithDebInfoAssert/ swift-macosx-x86_64/
  5. 檔案架構 # Original source file ${ROOT}/swift/stdlib/public/core # Converted swift files

    from gyb ${ROOT}/build/Ninja-RelWithDebInfoAssert/ swift-macosx-x86_64/stdlib/public/core/8
  6. @_fixed_layout public struct UInt8 : FixedWidthInteger, UnsignedInteger, _ExpressibleByBuiltinIntegerLiteral { ///

    A type that represents an integer literal. public typealias IntegerLiteralType = UInt8 @_transparent public init(_builtinIntegerLiteral x: _MaxBuiltinIntegerType) { _value = Builtin.s_to_u_checked_trunc_Int2048_Int8(x).0 } @_transparent public init(bitPattern x: Int8) { _value = x._value }
  7. Tuple let a = (1, 2, 3, 4, 5, 6)

    let b = (1, 2, 3, 4, 5, 6) print("a == b ? \(a == b)") a == b ? true
  8. Tuple let c = (1, 2, 3, 4, 5, 6,

    7) let d = (1, 2, 3, 4, 5, 6, 7) print("c == d ? \(c == d)") error: binary operator '==' cannot be applied to two '(Int, Int, Int, Int, Int, Int, Int)' operands
  9. Tuple’s GYB % for arity in range(2,7): % typeParams =

    [chr(ord("A") + i) for i in range(arity)] % tupleT = "({})".format(",".join(typeParams)) % equatableTypeParams = ", ".join(["{} : Equatable".format(c) for c in typeParams]) % originalTuple = "(\"a\", {})".format(", ".join(map(str, range(1, arity)))) % greaterTuple = "(\"a\", {})".format(", ".join(map(str, range(1, arity - 1) + [arity]))) // Tuple.swift.gyb
  10. public func == <A : Comparable, B : Comparable, C

    : Comparable, D : Comparable, E : Comparable, F : Comparable>(lhs: (A,B,C,D,E,F), rhs: (A,B,C,D,E,F)) -> Bool {
 guard lhs.0 == rhs.0 { return false } return ( lhs.1, lhs.2, lhs.3, lhs.4, lhs.5 ) == ( rhs.1, rhs.2, rhs.3, rhs.4, rhs.5 )
 
 } // Tuple.swift
  11. Optional @_frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral { case none

    case some(Wrapped) @_transparent public init(_ some: Wrapped) { self = .some(some) } @_transparent public init(nilLiteral: ()) { self = .none } }
  12. Optional - Map public func map<U>( _ transform: (Wrapped) throws

    -> U ) rethrows -> U? {
 switch self { case .some(let y): return .some(try transform(y)) case .none: return .none } }
  13. Optional - flatMap public func flatMap<U>( _ transform: (Wrapped) throws

    -> U ) rethrows -> U? {
 switch self { case .some(let y): return try transform(y) case .none: return .none } }
  14. Optional - Unwrap @_transparent public func ?? <T>(optional: T?, defaultValue:

    @autoclosure () throws -> T?) rethrows -> T? { switch optional { case .some(let value): return value case .none: return try defaultValue() } }
  15. Bool @_fixed_layout public struct Bool { @usableFromInline internal var _value:

    Builtin.Int1 public init() { let zero: Int8 = 0 self._value = Builtin.trunc_Int8_Int1(zero._value) } }
  16. How to @autoclosure if measurement.count > 0 && sum /

    Double(measurement.count) < 5 { // do somehting.. } measurement.count might be ZERO!!
  17. How to @autoclosure @_transparent @inline(__always) public static func && (

    lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool { return lhs ? try rhs() : false }
  18. Attributes • @inlinable :expose not interface but source code, work

    when -O • @_transparent: must do inline even when -Onone • @_fixed_layout: Processed at SIL stage. It tells compiler that access properties by offset is possible without looking up metadata.
 

  19. Attributes • @usableFromInline : Temporarily change scope only during inline

    stage. Include @inlinable
 
 
 
 
 
 
 
 @_fixed_layout public struct Bool { @usableFromInline internal var _value: Builtin.Int1 // …
  20. Integer Again public struct Int : FixedWidthInteger, SignedInteger, _ExpressibleByBuiltinIntegerLiteral {

    
 public init(_builtinIntegerLiteral x: _MaxBuiltinIntegerType) { _value = Builtin.s_to_s_checked_trunc_Int2048_Int64(x).0 }
  21. + public static func +(lhs: Int, rhs: Int) -> Int

    { var lhs = lhs lhs += rhs return lhs }
  22. += public static func +=(lhs: inout Int, rhs: Int) {

    let (result, overflow) = Builtin.sadd_with_overflow_Int64( lhs._value, rhs._value, true._value) Builtin.condfail(overflow) lhs = Int(result) }
  23. You may surprise… • Int is a struct in standard

    library • + is a global function declared in standard library • What an inefficient way !! Do we really implement basic arithmetic operations by cross-module function calls?
 

  24. ObjC & Swift Compilation Clang Frontend ObjC LLVM LLVM IR

    Machine Code Swift Frontend Swift IRGen SIL Machine Code LLVM LLVM IR
  25. Example // sum.swift let a = 1 let b =

    2 let c = a + b $swiftc -emit-ir sum.swift
  26. LLVM IR define i32 @main(i32, i8**) #0 { entry: %2

    = bitcast i8** %1 to i8* store i64 1, i64* getelementptr inbounds (%TSi, %TSi* @"$S4test1aSivp", i32 0, i32 0), align 8 store i64 2, i64* getelementptr inbounds (%TSi, %TSi* @"$S4test1bSivp", i32 0, i32 0), align 8 %3 = load i64, i64* getelementptr inbounds (%TSi, %TSi* @"$S4test1aSivp", i32 0, i32 0), align 8 %4 = load i64, i64* getelementptr inbounds (%TSi, %TSi* @"$S4test1bSivp", i32 0, i32 0), align 8 %5 = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %3, i64 %4) %6 = extractvalue { i64, i1 } %5, 0 %7 = extractvalue { i64, i1 } %5, 1 br i1 %7, label %9, label %8
  27. llvm::Intrinsic::ID swift::getLLVMIntrinsicIDForBuiltinWithOverflow(BuiltinValue Kind ID) { switch (ID) { default: break;

    case BuiltinValueKind::SAddOver: return llvm::Intrinsic::sadd_with_overflow; case BuiltinValueKind::UAddOver: return llvm::Intrinsic::uadd_with_overflow; case BuiltinValueKind::SSubOver: return llvm::Intrinsic::ssub_with_overflow; case BuiltinValueKind::USubOver: return llvm::Intrinsic::usub_with_overflow; case BuiltinValueKind::SMulOver: return llvm::Intrinsic::smul_with_overflow; case BuiltinValueKind::UMulOver: return llvm::Intrinsic::umul_with_overflow; } llvm_unreachable("Cannot convert the overflow builtin to llvm intrinsic."); } ${ROOT}/swift/lib/AST/Builtin.cpp
  28. How To Use Builtin. // builtin_add.swift import Swift let (result,

    overflow) = Builtin.sadd_with_overflow_Int64(1._value, 2._value, true._getBuiltinLogicValue()) print(Int(result)) $swiftc -parse-stdlib builtin_add.swift && ./builtin_add
  29. @usableFromInline // builtin_add.swift import Swift let (result, overflow) = Builtin.sadd_with_overflow_Int64(1._value,

    2._value, true._value) print(Int(result)) error: '_value' is inaccessible due to 'internal' protection level
  30. Sequence public protocol Sequence { associatedtype Element associatedtype Iterator :

    IteratorProtocol where Iterator.Element == Element func makeIterator() -> Iterator }
 public protocol IteratorProtocol { mutating func next() -> Element? }
  31. Sequence public protocol Sequence { associatedtype Element associatedtype Iterator :

    IteratorProtocol where Iterator.Element == Element func makeIterator() -> Iterator }
 public protocol IteratorProtocol { mutating func next() -> Element? }
  32. Enumerated /// - Complexity: O(1) @inlinabl public func enumerated() ->

    EnumeratedSequence<Self> { return EnumeratedSequence(_base: self) }
  33. for-in EnumeratedSequence extension EnumeratedSequence { public struct Iterator { internal

    var _base: Base.Iterator internal var _count: Int internal init(_base: Base.Iterator) { self._base = _base self._count = 0 } } }
  34. for-in EnumeratedSequence extension EnumeratedSequence.Iterator: IteratorProtocol, Sequence { public typealias Element

    = (offset: Int, element: Base.Element) public mutating func next() -> Element? { guard let b = _base.next() else { return nil } let result = (offset: _count, element: b) _count += 1 return result } }
  35. Map public func map<T>( _ transform: (Element) throws -> T

    ) rethrows -> [T] {
 let initialCapacity = underestimatedCount var result = ContiguousArray<T>() result.reserveCapacity(initialCapacity) var iterator = self.makeIterator() // lower half of map func
  36. Map // upper half of map func for _ in

    0..<initialCapacity { result.append(try transform(iterator.next()!)) } while let element = iterator.next() { result.append(try transform(element)) } return Array(result) }
  37. Reduce public func reduce<Result>( _ initialResult: Result, _ nextPartialResult: (_

    partialResult: Result, Element) throws -> Result ) rethrows -> Result {
 var accumulator = initialResult for element in self { accumulator = try nextPartialResult(accumulator, element) }
 return accumulator }
  38. Reduce Into public func reduce<Result>( into initialResult: Result, _ updateAccumulatingResult:

    (_ partialResult: inout Result, Element) throws -> () ) rethrows -> Result { var accumulator = initialResult for element in self { try updateAccumulatingResult(&accumulator, element) } return accumulator } }
  39. flatMap public func flatMap<SegmentOfResult : Sequence>( _ transform: (Element) throws

    -> SegmentOfResult ) rethrows -> [SegmentOfResult.Element] {
 var result: [SegmentOfResult.Element] = [] for element in self { result.append(contentsOf: try transform(element)) }
 return result }
  40. compactMap public func compactMap<ElementOfResult>( _ transform: (Element) throws -> ElementOfResult?

    ) rethrows -> [ElementOfResult] {
 return try _compactMap(transform)
 }
  41. public func _compactMap<ElementOfResult>( _ transform: (Element) throws -> ElementOfResult? )

    rethrows -> [ElementOfResult] {
 var result: [ElementOfResult] = []
 for element in self { if let newElement = try transform(element) { result.append(newElement) } }
 return result }
  42. class FileReadIterator: IteratorProtocol { 
 private let handler = FileHandle(forReadingAtPath:

    "/path/to/large_file")! func next() -> UInt8? { let data = handler.readData(ofLength: 1) return data[0] } 
 deinit { handler.closeFile() } }
  43. LazySequence public struct LazySequence<Base : Sequence>: _SequenceWrapper {
 public var

    _base: Base internal init(_base: Base) { self._base = _base } }
  44. LazySequenceProtocol extension LazySequenceProtocol { public func map<U>( _ transform: @escaping

    (Elements.Element) -> U ) -> LazyMapSequence<Self.Elements, U> { return LazyMapSequence(_base: self.elements, transform: transform) } }
  45. public struct LazyMapSequence<Base : Sequence, Element> { internal var _base:

    Base internal let _transform: (Base.Element) -> Element internal init(_base: Base, transform: @escaping (Base.Element) -> Element) {
 self._base = _base self._transform = transform
 } }
  46. File Reader Revised let fileSequence = AnySequence { FileReadIterator() }


    let lazyBatch = fileSequence.lazy.map{ print($0)} var batchIterator = lazyBatch.makeIterator() for _ in 1 ... 10 { batchIterator.next() }
  47. File Reader Revised let fileSequence = AnySequence { FileReadIterator() }


    let lazyBatch = fileSequence.lazy.map{ print($0)} var batchIterator = lazyBatch.makeIterator() for _ in 1 ... 1024 { batchIterator.next() }
  48. Collection removeAll(where:) extension RangeReplaceableCollection {
 public mutating func removeAll( where

    shouldBeRemoved: (Element) throws -> Bool ) rethrows {
 let suffixStart = try _halfStablePartition(isSuffixElement: shouldBeRemoved)
 removeSubrange(suffixStart…) } }
  49. mutating func _halfStablePartition( isSuffixElement: (Element) throws -> Bool ) rethrows

    -> Index {
 guard var i = try firstIndex(where: isSuffixElement) else { return endIndex } var j = index(after: i) while j != endIndex { if try !isSuffixElement(self[j]) { swapAt(i, j); formIndex(after: &i) } formIndex(after: &j) }
 return i }
  50. mutating func _halfStablePartition( isSuffixElement: (Element) throws -> Bool ) rethrows

    -> Index {
 guard var i = try firstIndex(where: isSuffixElement) else { return endIndex } var j = index(after: i)
 while j != endIndex { if try !isSuffixElement(self[j]) { swapAt(i, j) formIndex(after: &i) } formIndex(after: &j) }
 return i } OH! It’s moving zeros!
  51. Inside Array public struct Array<Element>: _DestructorSafeContainer { #if _runtime(_ObjC) internal

    typealias _Buffer = _ArrayBuffer<Element> #endif internal var _buffer: _Buffer internal init(_buffer: _Buffer) { self._buffer = _buffer } }
  52. Copy On Write mutating func append(_ newElement: __owned Element) {

    _makeUniqueAndReserveCapacityIfNotUnique()
 let oldCount = _getCount() _reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
 _appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement) }
  53. Copy If Not Unique mutating func _makeUniqueAndReserveCapacityIfNotUnique() { if _slowPath(!

    _buffer.isMutableAndUniquelyReferenced()) { _copyToNewBuffer(oldCount: _buffer.count) } }
  54. isUnique mutating func isUniquelyReferenced() -> Bool {
 if !_isClassOrObjCExistential(Element.self) {

    return _storage.isUniquelyReferenced_native_noSpareBits() } if !_storage.isUniquelyReferencedNative() { return false }
 return _isNative }

  55. Copy To New Buffer mutating func _copyToNewBuffer(oldCount: Int) { let

    newCount = oldCount + 1 var newBuffer = _buffer._forceCreateUniqueMutableBuffer( countForNewBuffer: oldCount, minNewCapacity: newCount) _buffer._arrayOutOfPlaceUpdate(&newBuffer, oldCount, 0) }
  56. What If Reach Full Capacity func _forceCreateUniqueMutableBufferImpl( countForBuffer: Int, minNewCapacity:

    Int, requiredCapacity: Int ) -> _ContiguousArrayBuffer<Element> { let minimumCapacity = Swift.max(requiredCapacity, minNewCapacity > capacity ? _growArrayCapacity(capacity) : capacity) return _ContiguousArrayBuffer( _uninitializedCount: countForBuffer, minimumCapacity: minimumCapacity) }
  57. Array Is Not Thread Safe import Dispatch var array =

    [Int]() DispatchQueue.concurrentPerform(iterations: 50) { index in let last = array.last ?? 0 array.append(last + 1) } print("array count: \(array.count)")
  58. var array = [1, 2, 3, 4, 5] let arrayAccessQueue

    = DispatchQueue(label: "array", qos: .utility, attributes: .concurrent) // read var readValue: Int = 0 arrayAccessQueue.sync { readValue = array[0] } // write arrayAccessQueue.async(flags: .barrier) { array.append(6) } // thread_safe_access_array.swift
  59. Sorting Array mutating func sort( by areInIncreasingOrder: (Element, Element) throws

    -> Bool ) rethrows { let didSortUnsafeBuffer = try _withUnsafeMutableBufferPointerIfSupported { buffer -> Void? in try buffer.sort(by: areInIncreasingOrder) } if didSortUnsafeBuffer == nil { try _introSort(within: startIndex..<endIndex, by: areInIncreasingOrder) } }
  60. Insertion / Intro / Heap mutating func _introSortImpl( within range:

    Range<Index>, by areInIncreasingOrder: (Element, Element) throws -> Bool, depthLimit: Int ) rethrows { if distance(from: range.lowerBound, to: range.upperBound) < 20 { try _insertionSort(within: range, by: areInIncreasingOrder) } else if depthLimit == 0 { try _heapSort(within: range, by: areInIncreasingOrder) } else { let partIdx = try _partition(within: range, by: areInIncreasingOrder) try _introSortImpl( within: range.lowerBound..<partIdx, by: areInIncreasingOrder, depthLimit: depthLimit &- 1) try _introSortImpl( within: partIdx..<range.upperBound, by: areInIncreasingOrder, depthLimit: depthLimit &- 1) } }
  61. Sorting Strategy mutating func _introSortImpl( within range: Range<Index>, by areInIncreasingOrder:

    (Element, Element) throws -> Bool, depthLimit: Int ) rethrows { if distance(from: range.lowerBound, to: range.upperBound) < 20 { try _insertionSort(within: range, by: areInIncreasingOrder) } else if depthLimit == 0 { try _heapSort(within: range, by: areInIncreasingOrder) } else { let partIdx = try _partition(within: range, by: areInIncreasingOrder) try _introSortImpl( within: range.lowerBound..<partIdx, by: areInIncreasingOrder, depthLimit: depthLimit &- 1) try _introSortImpl( within: partIdx..<range.upperBound, by: areInIncreasingOrder, depthLimit: depthLimit &- 1) } }
  62. Sorting Strategy mutating func _introSortImpl( within range: Range<Index>, by areInIncreasingOrder:

    (Element, Element) throws -> Bool, depthLimit: Int ) rethrows { if distance(from: range.lowerBound, to: range.upperBound) < 20 { try _insertionSort(within: range, by: areInIncreasingOrder) } else if depthLimit == 0 { try _heapSort(within: range, by: areInIncreasingOrder) } else { let partIdx = try _partition(within: range, by: areInIncreasingOrder) try _introSortImpl( within: range.lowerBound..<partIdx, by: areInIncreasingOrder, depthLimit: depthLimit &- 1) try _introSortImpl( within: partIdx..<range.upperBound, by: areInIncreasingOrder, depthLimit: depthLimit &- 1) } }
  63. SequenceTypeTests.test("betterReduce") { let animals = ["Antelope", "Butterfly", "Camel", “Dolphin"] var

    timesClosureWasCalled = 0 let longestAnimal = animals.betterReduce { current, animal in timesClousreWasCalled += 1 if current.count > animal.count { return current } else { return animal } } ?? “" // expects … } // ${ROOT}/swift/validation-test/stdlib/SequenceType.swift.gyb
  64. SequenceTypeTests.test("betterReduce") { // Codes … expectEqual(longestAnimal, “Butterfly") expectEqual( animals.count, timesClosureWasCalled,

    "betterReduce() should be eager”) } // ${ROOT}/swift/validation-test/stdlib/SequenceType.swift.gyb
  65. // SequenceAlgorithm.swift — extension Sequence @inlinable public func betterReduce( _

    nextPartialResult: (_ partialResult: Element, Element) throws -> Element ) rethrows -> Element? {
 var i = makeIterator() guard var accumulated = i.next() else { return nil } while let element = i.next() { accumulated = try nextPartialResult(accumulated, element) } return accumulated }
  66. Test Standard Library Only ./llvm/utils/lit/lit.py -sv ${BUILD_ROOT}/swift- macosx-x86_64/test-macosx-x86_64/stdlib A full-test

    run is suggested in the first place ./llvm/utils/lit/lit.py -sv ${BUILD_ROOT}/ swift-macosx-x86_64/validation-test-macosx- x86_64/stdlib
  67. ${BUILD_ROOT} ninja swift-stdlib Thanks to ninja, lets build standard library

    only sh ${BUILD_RTTO}/validation-test-macosx-x86_64/
 stdlib/Output/SequenceType.swift.gyb.script And do test!!