Slide 1

Slide 1 text

Tuples conform to Equatable, Comparable, and Hashableͷ࣮૷ Θ͍Θ͍swiftc #22 @kateinoigakukun 1

Slide 2

Slide 2 text

ࣗݾ঺հ @kateinoigakukun • Merpay Expert Team Πϯλʔϯ • SwiftWasmϝϯςφ 2

Slide 3

Slide 3 text

ࠓ·Ͱͷ໰୊ let points = [(x: 128, y: 316), (x: 0, y: 0), (x: 100, y: 42)] let origin = (x: 0, y: 0) // error: type '(x: Int, y: Int)' cannot conform to 'Equatable'; // only struct/enum/class types can conform to protocols if points.contains(origin) { // do some serious calculations here } // error: type '(x: Int, y: Int)' cannot conform to 'Comparable'; // only struct/enum/class types can conform to protocols let sortedPoints = points.sorted() // error: type '(x: Int, y: Int)' cannot conform to 'Hashable'; // only struct/enum/class types can conform to protocols let uniquePoints = Set(points) 3

Slide 4

Slide 4 text

λϓϧ͕͋Δͱࣗಈ࣮૷΋Ͱ͖ͳ͘ͳͬͯͨ struct Restaurant { let name: String let location: (latitude: Int, longitude: Int) } // error: type 'Restaurant' does not conform to protocol 'Equatable' extension Restaurant: Equatable {} // error: type 'Restaurant' does not conform to protocol 'Hashable' extension Restaurant: Hashable {} 4

Slide 5

Slide 5 text

࣮͸͢ͰʹΦʔόʔϩʔυ͸͋Δ SE-0015: Tuple comparison operators .gybͰίʔυੜ੒͞ΕͯΔɻ @warn_unused_result public func == (lhs: (A,B,C), rhs: (A,B,C)) -> Bool { return lhs.0 == rhs.0 && lhs.1 == rhs.1 && lhs.2 == rhs.2 } @warn_unused_result public func < (lhs: (A,B,C), rhs: (A,B,C)) -> Bool { if lhs.0 != rhs.0 { return lhs.0 < rhs.0 } if lhs.1 != rhs.1 { return lhs.1 < rhs.1 } return lhs.2 < rhs.2 } ... 5

Slide 6

Slide 6 text

SE-0283ͱ͸ શͯͷཁૉ͕EquatableɺComparableɺHashableͷ͍ͣΕ͔ʹ ४ڌ͍ͯ͠Δ৔߹ɺλϓϧࣗମ΋ͦͷϓϩτίϧʹ४ڌ͢Δ ϓϩϙʔβϧ͸डཧ͞Εɺ࣮૷΋͋Δ͕ϝΠϯετϦʔϜʹ͸ ೖͬͯͳ͍ https://github.com/apple/swift-evolution/blob/master/proposals/0283-tuples-are-equatable-comparable-hashable.md 6

Slide 7

Slide 7 text

func isEqual(_ lhs: T, _ rhs: T) -> Bool { lhs == rhs } // Ok, Int is Equatable thus the tuples are Equatable isEqual((1, 2, 3), (1, 2, 3)) // true struct EmptyStruct {} // error: type '(EmptyStruct, Int, Int)' does not conform to protocol 'Equatable' // note: value of type 'EmptyStruct' does not conform to protocol 'Equatable', // preventing conformance of '(EmptyStruct, Int, Int)' to 'Equatable' isEqual((EmptyStruct(), 1, 2), (EmptyStruct(), 1, 2)) // We don't take into account the labels for equality. isEqual((x: 0, y: 0), (0, 0)) // true 7

Slide 8

Slide 8 text

func isLessThan(_ lhs: T, _ rhs: T) -> Bool { lhs < rhs } let origin = (x: 0, y: 0) let randomPoint = (x: Int.random(in: 1 ... 10), y: Int.random(in: 1 ... 10)) // In this case, the first element of origin is 0 and the first element // of randomPoint is somewhere between 1 and 10, so they are not equal. // origin's element is less than randomPoint's, thus true. isLessThan(origin, randomPoint) // true // We don't take into account the labels for comparison. isLessThan((x: 0, y: 0), (1, 0)) // true 8

Slide 9

Slide 9 text

let points = [(x: 0, y: 0), (x: 1, y: 2), (x: 0, y: 0)] let uniquePoints = Set(points) // Create a grid system to hold game entities. var grid = [(x: Int, y: Int): Entity]() for point in uniquePoints { grid[point]?.move(up: 10) } // We don't take into account the labels for hash value. (x: 0, y: 0).hashValue == (0, 0).hashValue // true grid[(x: 100, y: 200)] = Entity(name: "Pam") print(grid[(100, 200)]) // Entity(name: "Pam") 9

Slide 10

Slide 10 text

࣮૷ํ਑ λϓϧͷϓϩτίϧద߹ΛҰൠԽ͢Δͷ͸ϋʔυͳͷͰɺ EquatableɺComparableɺHashableʹରͯ͠ΞυϗοΫͳ࣮૷ Λ͢ΔɻίϯύΠϥతʹ͸ٕज़ෛ࠴ʹͳΔ͕ɺଟ͘ͷϢʔβʔ Λٹ͏͜ͱΛ༏ઌͯ͠Δɻ ࢀߟɿSpecial Case Protocol Conformance: Long-term Tradeoffs for Near-term Conformance 10

Slide 11

Slide 11 text

• ຊ౰͸͜͏͍͏ίʔυΛඪ४ϥΠϒϥϦʹೖΕ͓͚ͯͩ͘ʹ ͍ͨ͠ɻ • ͕ɺҰൠԽ͢Δʹ͸Մม௕δΣωϦΫεͳͲͷॏΊͷݴޠػ ೳ͕ඞཁ extension Tuple: Equatable where T1: Equatable, T2: Equatable, ... { public static func ==(_ lhs: Self, _ rhs: Self) -> Bool { ... } } 11

Slide 12

Slide 12 text

࣮૷಺༰ PR apple/swift#28833 1. ௨ৗͷϓϩτίϧίϯϑΥʔϚϯεͷଞʹ BuiltinProtocolConformanceͷ֓೦Λ௥Ճ 2. ܕਪ࿦ثͷରԠ 3. ϥϯλΠϜϥΠϒϥϦʹBuiltinProtocolConformanceΛ࣮૷ 4. IRGenͰBuiltinProtocolConformanceΛࢀর͢ΔΑ͏ʹίʔ υੜ੒ ࠓ೔ͷϝΠϯτϐοΫ͸3ͱ4 12

Slide 13

Slide 13 text

⾠ આ໌ͷͨΊɺٙࣅSwiftΛ࢖ͬͨΓɺ࣮ࡍ͸C++Ͱॻ͔Εͨϥ ϯλΠϜؔ਺ΛSwiftͰ؆୯ʹॻ͖௚ͨ͠Γͯ͠·͢ɻ 13

Slide 14

Slide 14 text

௨ৗͷϓϩτίϧద߹ WitnessTable͕੩తʹܾ·Δ৔߹ 14

Slide 15

Slide 15 text

func isEqual(_ lhs: T, _ rhs: T) -> Bool { lhs == rhs } _ = isEqual(1, 2) // false // ↓తͳײ͡ʹίϯύΠϧ͞ΕΔ func isEqual(_ lhs: T, _ rhs: T, type: T.Type, witnessTable: WitnessTable) -> Bool { let fn = witnessTable[Equatable.==] return fn(lhs, rhs) } let Int_Equatable_WitnessTable: WitnessTable = [ Equatable.==: Int.==, ] _ = isEqual(1, 2, Int.self, Int_Equatable_WitnessTable) // false 15

Slide 16

Slide 16 text

௨ৗͷϓϩτίϧద߹ WitnessTable͕ಈతʹܾ·Δ৔߹ 16

Slide 17

Slide 17 text

ίϯύΠϧલ public struct Box { public let value: T } extension Box: CustomStringConvertible where T: CustomStringConvertible { var description: String { value.description } } func stringify(_ target: T) -> String { return target.description } func boxStringify(_ target: T) -> String { return stringify(Box(value: target)) } _ = boxStringify(1) // "1" 17

Slide 18

Slide 18 text

ίϯύΠϧޙ func stringify(_ target: T, type: T.Type, witnessTable: WitnessTable) -> String? { return witnessTable.description(target) } func boxStringify(_ target: T, type: T.Type, witnessTable: WitnessTable) -> String { let witnessTable = swift_getWitnessTable(Box.self, CustomStringConvertible.self) return stringify(Box(value: target), Box.self, witnessTable) } let Int_CustomStringConvertible_WitnessTable: WitnessTable = [ CustomStringConvertible.description: Int.description, ] _ = boxStringify(1, Int.self, Int_CustomStringConvertible_WitnessTable) // "1" 18

Slide 19

Slide 19 text

swift_conformsToProtocol͸Ͳ͏΍ͬͯWitnessTableΛऔ ಘͯ͠Δͷʁ // ඪ४ϥΠϒϥϦ public protocol CustomStringConvertible { ... } public extension Int: CustomStringConvertible { ... } let Int_CustomStringConvertible_WitnessTable: WitnessTable = [ CustomStringConvertible.description: Int.description, ] let localConformances = [ Conformance( type: Int.self, conforming: CustomStringConvertible.self, witnessTable: Int_CustomStringConvertible_WitnessTable ), ... ] _registerProtocolConformances(localConformances) 19

Slide 20

Slide 20 text

// ϥϯλΠϜϥΠϒϥϦ var globalConformances: [Conformance] = [] func _registerProtocolConformances(_ conformances: [Conformance]) { globalConformances.append(conformances) } func swift_getWitnessTable(_ type: Any.Type, _ proto: Protocol.Type) -> WitnessTable? { for conformance in globalConformances { if conformance.type == type && conformance.conforming == proto { return conformance.witnessTable } } return nil } https://github.com/apple/swift/blob/41f74e4/stdlib/public/runtime/ImageInspectionCommon.cpp 20

Slide 21

Slide 21 text

BuiltinProtocolConformance for λϓϧ ͭ·Γɺλϓϧ޲͚ͷWitness TableͱConformanceΛϥϯλΠ ϜϥΠϒϥϦʹ࡞͓͚ͬͯ͹ɺ੩త/ಈతͳϢʔεέʔεʹରԠ Ͱ͖Δɻ 21

Slide 22

Slide 22 text

खॻ͖ Tuple.== bool swift::_swift_tupleEquatable_equals(OpaqueValue *tuple1, OpaqueValue *tuple2, SWIFT_CONTEXT Metadata *swiftSelf, Metadata *Self, void *witnessTable) { auto tuple = cast(Self); auto table = reinterpret_cast(witnessTable); // Loop through all elements, and check if both tuples element is equal. for (size_t i = 0; i != tuple->NumElements; i += 1) { auto elt = tuple->getElement(i); // Get the element conformance from the private data in the witness table. auto conformance = reinterpret_cast(table[-1 - i]); auto value1 = reinterpret_cast( reinterpret_cast(tuple1) + elt.Offset); auto value2 = reinterpret_cast( reinterpret_cast(tuple2) + elt.Offset); // Grab the specific witness for this element type. auto equatableTable = reinterpret_cast(conformance); auto equalsWitness = equatableTable[WitnessTableFirstRequirementOffset]; auto equals = reinterpret_cast(equalsWitness); // Call the equal function auto result = equals(value1, value2, elt.Type, elt.Type, conformance); // If the values aren't equal, this tuple isn't equal. :) if (!result) return false; } // Otherwise this tuple has value equality with all elements. return true; } https://github.com/apple/swift/pull/28833/files#diff-c289d2fa8030a8b0648018ab48d060bb32554aa01b96399c406702089ef0033cR148-R184 22

Slide 23

Slide 23 text

खॻ͖Protocol Conformance Descriptor __asm( #if defined(__ELF__) " .type __swift_tupleEquatable_private, @object\n" " .local __swift_tupleEquatable_private\n" " .comm __swift_tupleEquatable_private, 128, 16\n" " .protected " TUPLE_EQUATABLE_CONF "\n" " .type " TUPLE_EQUATABLE_CONF ", @object\n" " .section .rodata\n" #elif defined(__MACH__) " .zerofill __DATA, __bss, __swift_tupleEquatable_private, 128, 4\n" " .section __TEXT, __const\n" #elif defined(_WIN32) " .lcomm __swift_tupleEquatable_private, 128, 16\n" " .section .rdata, \"dr\"\n" #pragma comment(linker, "/EXPORT:_swift_tupleEquatable_conf,DATA") #endif " .globl " TUPLE_EQUATABLE_CONF "\n" " .p2align 2\n" TUPLE_EQUATABLE_CONF ":\n" // This is an indirectable relative reference to the GOT entry for the // Equatable protocol descriptor. " .long " INDIRECT_RELREF_GOTPCREL(EQUATABLE_DESCRIPTOR_SYMBOL) "\n" // 769 is the MetadataKind::Tuple " .long 769\n" // This indicates that we have no witness table pattern. We use a generic // witness table for builtin conformances. " .long 0\n" // 196640 are the ConformanceFlags with the type reference bit set to // MetadataKind, the has resilient witness bit, and the generic witness table // bit. " .long 196640\n" // This 1 is the ResilientWitnessesHeader indicating we have 1 resilient // witness. " .long 1\n" // This is an indirectable relative reference to the GOT entry for the // Equatable == method descriptor. " .long " INDIRECT_RELREF_GOTPCREL(EQUATABLE_EE_METHOD_DESCRIPTOR) "\n" // This is a direct relative reference to the equals witness defined below. " .long (" TUPLE_EQUATABLE_EQUALS ") - .\n" // The witness table size in words. " .short 0\n" // The witness table private size in words & requires instantiation. " .short 1\n" // The witness table instantiator function. " .long 0\n" // This is a direct relative reference to the private data for the // conformance. " .long __swift_tupleEquatable_private - .\n" #if defined(__ELF__) " .size " TUPLE_EQUATABLE_CONF ", 40\n" #endif ); 23

Slide 24

Slide 24 text

extern "C" const ProtocolConformanceDescriptor _swift_tupleEquatable_conf; extern "C" const ProtocolConformanceDescriptor _swift_tupleComparable_conf; extern "C" const ProtocolConformanceDescriptor _swift_tupleHashable_conf; static const ProtocolConformanceDescriptor *getTupleConformanceDescriptor( const ProtocolDescriptor *protocol) { if (protocol == &EQUATABLE_DESCRIPTOR) { return &_swift_tupleEquatable_conf; } if (protocol == &COMPARABLE_DESCRIPTOR) { return &_swift_tupleComparable_conf; } if (protocol == &HASHABLE_DESCRIPTOR) { return &_swift_tupleHashable_conf; } return nullptr; } static const WitnessTable * swift_conformsToProtocolImpl(const Metadata *const type, const ProtocolDescriptor *protocol) { ... // If we're asking if a tuple conforms to a protocol, handle that here for // builtin conformances. if (auto tuple = dyn_cast(type)) { if (tupleConformsToProtocol(tuple, protocol)) { auto descriptor = getTupleConformanceDescriptor(protocol); C.cacheResult(type, protocol, descriptor->getWitnessTable(type), /*always cache*/ 0); } } ... } 24

Slide 25

Slide 25 text

੩తͳProtocol Conformance Descriptorͷࢀর std::string IRGenMangler::mangleProtocolConformanceDescriptor( const RootProtocolConformance *conformance) { // Builtin conformances are different because they don't use a mangled name // for their conformance descriptors. For now, we use predefined symbol names // just in case in the future we have some conflict between actual // conformances and these builtin ones. if (isa(conformance)) { auto &ctx = conformance->getType()->getASTContext(); if (conformance->getType()->is()) { auto equatable = ctx.getProtocol(KnownProtocolKind::Equatable); auto comparable = ctx.getProtocol(KnownProtocolKind::Comparable); auto hashable = ctx.getProtocol(KnownProtocolKind::Hashable); if (conformance->getProtocol() == equatable) { return "_swift_tupleEquatable_conf"; } if (conformance->getProtocol() == comparable) { return "_swift_tupleComparable_conf"; } if (conformance->getProtocol() == hashable) { return "_swift_tupleHashable_conf"; } llvm_unreachable("mangling conformance descriptor for unknown tuple \ protocol"); } llvm_unreachable("mangling conformance descriptor for unknown builtin type"); } ... } 25

Slide 26

Slide 26 text

͕͜͜ਏ͍ΑBuiltinProtocolConformance • ݴΘͣ΋͕ͳΠϯϥΠϯΞηϯϒϦ͕ਏ͍ 26

Slide 27

Slide 27 text

ճආࡦ1 • ίϯύΠϥͰඪ४ϥΠϒϥϦͷͨΊ͚ͩʹίʔυੜ੒͢Δ 27

Slide 28

Slide 28 text

ճආࡦ2 • LLVM IRͰॻ͘ 28