Skip to content

Commit 0d3fe63

Browse files
committed
SmallProjectionPath: model existential projections and indexed elements
add field types: `x`: existential address projection, like `open_existential_addr` `i<n>`: indexed element with constant index, like `index_addr` with an integer literal as index `i*`: indexed element with an unknown index
1 parent b03ef3c commit 0d3fe63

File tree

1 file changed

+144
-10
lines changed

1 file changed

+144
-10
lines changed

SwiftCompilerSources/Sources/SIL/SmallProjectionPath.swift

Lines changed: 144 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,22 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
7272
case tupleField = 0x2 // A concrete tuple element: syntax e.g. `2`
7373
case enumCase = 0x3 // A concrete enum case (with payload): syntax e.g. `e4'
7474
case classField = 0x4 // A concrete class field: syntax e.g. `c1`
75-
case tailElements = 0x5 // A tail allocated element of a class: syntax `ct`
76-
case anyValueFields = 0x6 // Any number of any value fields (struct, tuple, enum): syntax `v**`
75+
case indexedElement = 0x5 // A constant offset into an array of elements: syntax e.g. 'i2'
76+
// The index must not be 0 and there must not be two successive element indices in the path.
7777

7878
// "Large" kinds: starting from here the low 3 bits must be 1.
7979
// This and all following kinds (we'll add in the future) cannot have a field index.
80-
case anyClassField = 0x7 // Any class field, including tail elements: syntax `c*`
81-
case anything = 0xf // Any number of any fields: syntax `**`
80+
case tailElements = 0x07 // (0 << 3) | 0x7 A tail allocated element of a class: syntax `ct`
81+
case existential = 0x0f // (1 << 3) | 0x7 A concrete value projected out of an existential: synatx 'x'
82+
case anyClassField = 0x17 // (2 << 3) | 0x7 Any class field, including tail elements: syntax `c*`
83+
case anyIndexedElement = 0x1f // (3 << 3) | 0x7 An unknown offset into an array of elements.
84+
// There must not be two successive element indices in the path.
85+
case anyValueFields = 0x27 // (4 << 3) | 0x7 Any number of any value fields (struct, tuple, enum): syntax `v**`
86+
case anything = 0x2f // (5 << 3) | 0x7 Any number of any fields: syntax `**`
8287

8388
public var isValueField: Bool {
8489
switch self {
85-
case .anyValueFields, .structField, .tupleField, .enumCase:
90+
case .anyValueFields, .structField, .tupleField, .enumCase, .indexedElement, .anyIndexedElement, .existential:
8691
return true
8792
case .root, .anything, .anyClassField, .classField, .tailElements:
8893
return false
@@ -93,10 +98,19 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
9398
switch self {
9499
case .anyClassField, .classField, .tailElements:
95100
return true
96-
case .root, .anything, .anyValueFields, .structField, .tupleField, .enumCase:
101+
case .root, .anything, .anyValueFields, .structField, .tupleField, .enumCase, .indexedElement, .anyIndexedElement, .existential:
97102
return false
98103
}
99104
}
105+
106+
var isIndexedElement: Bool {
107+
switch self {
108+
case .anyIndexedElement, .indexedElement:
109+
return true
110+
default:
111+
return false
112+
}
113+
}
100114
}
101115

102116
public init() { self.bytes = 0 }
@@ -121,6 +135,9 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
121135
case .enumCase: s = "e\(idx)"
122136
case .classField: s = "c\(idx)"
123137
case .tailElements: s = "ct"
138+
case .existential: s = "x"
139+
case .indexedElement: s = "i\(idx)"
140+
case .anyIndexedElement: s = "i*"
124141
case .anything: s = "**"
125142
case .anyValueFields: s = "v**"
126143
case .anyClassField: s = "c*"
@@ -182,6 +199,17 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
182199
/// For example, pushing `s0` to `c3.e1` returns `s0.c3.e1`.
183200
public func push(_ kind: FieldKind, index: Int = 0) -> SmallProjectionPath {
184201
assert(kind != .anything || bytes == 0, "'anything' only allowed in last path component")
202+
if (kind.isIndexedElement) {
203+
if kind == .indexedElement && index == 0 {
204+
// Ignore zero indices
205+
return self
206+
}
207+
// "Merge" two successive indexed elements
208+
let (k, _, numBits) = top
209+
if (k.isIndexedElement) {
210+
return pop(numBits: numBits).push(.anyIndexedElement)
211+
}
212+
}
185213
var idx = index
186214
var b = bytes
187215
if (b >> 56) != 0 {
@@ -234,6 +262,11 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
234262
return pop(numBits: numBits)
235263
}
236264
return nil
265+
case .anyIndexedElement:
266+
if kind.isIndexedElement {
267+
return self
268+
}
269+
return pop(numBits: numBits).popIfMatches(kind, index: index)
237270
case kind:
238271
if let i = index {
239272
if i != idx { return nil }
@@ -294,6 +327,15 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
294327
}
295328
}
296329

330+
public func popIndexedElements() -> SmallProjectionPath {
331+
var p = self
332+
while true {
333+
let (k, _, numBits) = p.top
334+
if !k.isIndexedElement { return p }
335+
p = p.pop(numBits: numBits)
336+
}
337+
}
338+
297339
/// Pops the last class projection and all following value fields from the tail of the path.
298340
/// For example:
299341
/// `s0.e2.3.c4.s1` -> `s0.e2.3`
@@ -344,7 +386,9 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
344386
let (kind, _, subPath) = pop()
345387
if !kind.isClassField { return false }
346388
return subPath.matches(pattern: subPattern)
347-
case .structField, .tupleField, .enumCase, .classField, .tailElements:
389+
case .anyIndexedElement:
390+
return popIndexedElements().matches(pattern: subPattern)
391+
case .structField, .tupleField, .enumCase, .classField, .tailElements, .indexedElement, .existential:
348392
let (kind, index, subPath) = pop()
349393
if kind != patternKind || index != patternIdx { return false }
350394
return subPath.matches(pattern: subPattern)
@@ -373,6 +417,15 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
373417
}
374418
return subPath.push(lhsKind, index: lhsIdx)
375419
}
420+
if lhsKind.isIndexedElement || rhsKind.isIndexedElement {
421+
let subPath = popIndexedElements().merge(with: rhs.popIndexedElements())
422+
let subPathTopKind = subPath.top.kind
423+
assert(!subPathTopKind.isIndexedElement)
424+
if subPathTopKind == .anything || subPathTopKind == .anyValueFields {
425+
return subPath
426+
}
427+
return subPath.push(.anyIndexedElement)
428+
}
376429
if lhsKind.isValueField || rhsKind.isValueField {
377430
let subPath = popAllValueFields().merge(with: rhs.popAllValueFields())
378431
assert(!subPath.top.kind.isValueField)
@@ -407,6 +460,9 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
407460
if lhsKind == .anything || rhsKind == .anything {
408461
return true
409462
}
463+
if lhsKind == .anyIndexedElement || rhsKind == .anyIndexedElement {
464+
return popIndexedElements().mayOverlap(with: rhs.popIndexedElements())
465+
}
410466
if lhsKind == .anyValueFields || rhsKind == .anyValueFields {
411467
return popAllValueFields().mayOverlap(with: rhs.popAllValueFields())
412468
}
@@ -417,6 +473,29 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
417473
}
418474
return false
419475
}
476+
477+
/// Return true if this path is a sub-path of `rhs` or is equivalent to `rhs`.
478+
///
479+
/// For example:
480+
/// `s0` is a sub-path of `s0.s1`
481+
/// `s0` is not a sub-path of `s1`
482+
/// `s0.s1` is a sub-path of `s0.s1`
483+
/// `i*.s1` is not a sub-path of `i*.s1` because the actual field is unknown on both sides
484+
public func isSubPath(of rhs: SmallProjectionPath) -> Bool {
485+
let (lhsKind, lhsIdx, lhsBits) = top
486+
switch lhsKind {
487+
case .root:
488+
return true
489+
case .classField, .tailElements, .structField, .tupleField, .enumCase, .existential, .indexedElement:
490+
let (rhsKind, rhsIdx, rhsBits) = rhs.top
491+
if lhsKind == rhsKind && lhsIdx == rhsIdx {
492+
return pop(numBits: lhsBits).isSubPath(of: rhs.pop(numBits: rhsBits))
493+
}
494+
return false
495+
case .anything, .anyValueFields, .anyClassField, .anyIndexedElement:
496+
return false
497+
}
498+
}
420499
}
421500

422501
//===----------------------------------------------------------------------===//
@@ -490,8 +569,12 @@ extension StringParser {
490569
entries.append((.anyClassField, 0))
491570
} else if consume("v**") {
492571
entries.append((.anyValueFields, 0))
572+
} else if consume("i*") {
573+
entries.append((.anyIndexedElement, 0))
493574
} else if consume("ct") {
494575
entries.append((.tailElements, 0))
576+
} else if consume("x") {
577+
entries.append((.existential, 0))
495578
} else if consume("c") {
496579
guard let idx = consumeInt(withWhiteSpace: false) else {
497580
try throwError("expected class field index")
@@ -507,6 +590,11 @@ extension StringParser {
507590
try throwError("expected struct field index")
508591
}
509592
entries.append((.structField, idx))
593+
} else if consume("i") {
594+
guard let idx = consumeInt(withWhiteSpace: false) else {
595+
try throwError("expected index")
596+
}
597+
entries.append((.indexedElement, idx))
510598
} else if let tupleElemIdx = consumeInt() {
511599
entries.append((.tupleField, tupleElemIdx))
512600
} else if !consume(".") {
@@ -546,6 +634,7 @@ extension SmallProjectionPath {
546634
merging()
547635
matching()
548636
overlapping()
637+
subPathTesting()
549638
predicates()
550639
path2path()
551640

@@ -561,6 +650,11 @@ extension SmallProjectionPath {
561650
assert(k4 == .enumCase && i4 == 876)
562651
let p5 = SmallProjectionPath(.anything)
563652
assert(p5.pop().path.isEmpty)
653+
let p6 = SmallProjectionPath(.indexedElement, index: 1).push(.indexedElement, index: 2)
654+
let (k6, i6, p7) = p6.pop()
655+
assert(k6 == .anyIndexedElement && i6 == 0 && p7.isEmpty)
656+
let p8 = SmallProjectionPath(.indexedElement, index: 0)
657+
assert(p8.isEmpty)
564658
}
565659

566660
func parsing() {
@@ -575,7 +669,10 @@ extension SmallProjectionPath {
575669
.push(.enumCase, index: 6)
576670
.push(.anyClassField)
577671
.push(.tupleField, index: 2))
578-
672+
testParse("i3.x.i*", expect: SmallProjectionPath(.anyIndexedElement)
673+
.push(.existential)
674+
.push(.indexedElement, index: 3))
675+
579676
do {
580677
var parser = StringParser("c*.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.**")
581678
_ = try parser.parseProjectionPathFromSIL()
@@ -606,6 +703,10 @@ extension SmallProjectionPath {
606703
testMerge("s1.s1.c2", "s1.c2", expect: "s1.v**.c2")
607704
testMerge("s1.s0", "s2.s0", expect: "v**")
608705
testMerge("ct", "c2", expect: "c*")
706+
testMerge("i1", "i2", expect: "i*")
707+
testMerge("i*", "i2", expect: "i*")
708+
testMerge("s0.i*.e3", "s0.e3", expect: "s0.i*.e3")
709+
testMerge("i*", "v**", expect: "v**")
609710

610711
testMerge("ct.s0.e0.v**.c0", "ct.s0.e0.v**.c0", expect: "ct.s0.e0.v**.c0")
611712
testMerge("ct.s0.s0.c0", "ct.s0.e0.s0.c0", expect: "ct.s0.v**.c0")
@@ -635,18 +736,22 @@ extension SmallProjectionPath {
635736
testMatch("c*", "c1", expect: false)
636737
testMatch("c*", "ct", expect: false)
637738
testMatch("v**", "s0", expect: false)
739+
testMatch("i1", "i1", expect: true)
740+
testMatch("i1", "i*", expect: true)
741+
testMatch("i*", "i1", expect: false)
638742

639743
testMatch("s0.s1", "s0.s1", expect: true)
640744
testMatch("s0.s2", "s0.s1", expect: false)
641745
testMatch("s0", "s0.v**", expect: true)
642746
testMatch("s0.s1", "s0.v**", expect: true)
643747
testMatch("s0.1.e2", "s0.v**", expect: true)
644-
testMatch("s0.v**.e2", "v**", expect: true)
748+
testMatch("s0.v**.x.e2", "v**", expect: true)
645749
testMatch("s0.v**", "s0.s1", expect: false)
646750
testMatch("s0.s1.c*", "s0.v**", expect: false)
647751
testMatch("s0.v**", "s0.**", expect: true)
648752
testMatch("s1.v**", "s0.**", expect: false)
649753
testMatch("s0.**", "s0.v**", expect: false)
754+
testMatch("s0.s1", "s0.i*.s1", expect: true)
650755
}
651756

652757
func testMatch(_ lhsStr: String, _ rhsStr: String, expect: Bool) {
@@ -670,13 +775,17 @@ extension SmallProjectionPath {
670775
testOverlap("s0.c*.s2", "s0.c1.c2.s2", expect: false)
671776
testOverlap("s0.c*.s2", "s0.s2", expect: false)
672777

673-
testOverlap("s0.v**.s2", "s0.s3", expect: true)
778+
testOverlap("s0.v**.s2", "s0.s3.x", expect: true)
674779
testOverlap("s0.v**.s2.c2", "s0.s3.c1", expect: false)
675780
testOverlap("s0.v**.s2", "s1.s3", expect: false)
676781
testOverlap("s0.v**.s2", "s0.v**.s3", expect: true)
677782

678783
testOverlap("s0.**", "s0.s3.c1", expect: true)
679784
testOverlap("**", "s0.s3.c1", expect: true)
785+
786+
testOverlap("i1", "i*", expect: true)
787+
testOverlap("i1", "v**", expect: true)
788+
testOverlap("s0.i*.s1", "s0.s1", expect: true)
680789
}
681790

682791
func testOverlap(_ lhsStr: String, _ rhsStr: String, expect: Bool) {
@@ -690,6 +799,27 @@ extension SmallProjectionPath {
690799
assert(reversedResult == expect)
691800
}
692801

802+
func subPathTesting() {
803+
testSubPath("s0", "s0.s1", expect: true)
804+
testSubPath("s0", "s1", expect: false)
805+
testSubPath("s0.s1", "s0.s1", expect: true)
806+
testSubPath("i*.s1", "i*.s1", expect: false)
807+
testSubPath("ct.s1.0.i3.x", "ct.s1.0.i3.x", expect: true)
808+
testSubPath("c0.s1.0.i3", "c0.s1.0.i3.x", expect: true)
809+
testSubPath("s1.0.i3.x", "s1.0.i3", expect: false)
810+
testSubPath("v**.s1", "v**.s1", expect: false)
811+
testSubPath("i*", "i*", expect: false)
812+
}
813+
814+
func testSubPath(_ lhsStr: String, _ rhsStr: String, expect: Bool) {
815+
var lhsParser = StringParser(lhsStr)
816+
let lhs = try! lhsParser.parseProjectionPathFromSIL()
817+
var rhsParser = StringParser(rhsStr)
818+
let rhs = try! rhsParser.parseProjectionPathFromSIL()
819+
let result = lhs.isSubPath(of: rhs)
820+
assert(result == expect)
821+
}
822+
693823
func predicates() {
694824
testPredicate("v**", \.hasClassProjection, expect: false)
695825
testPredicate("v**.c0.s1.v**", \.hasClassProjection, expect: true)
@@ -737,6 +867,10 @@ extension SmallProjectionPath {
737867
testPath2Path("c0.s3", { $0.popIfMatches(.anyClassField) }, expect: nil)
738868
testPath2Path("**", { $0.popIfMatches(.anyClassField) }, expect: "**")
739869
testPath2Path("c*.e3", { $0.popIfMatches(.anyClassField) }, expect: "e3")
870+
871+
testPath2Path("i*.e3.s0", { $0.popIfMatches(.enumCase, index: 3) }, expect: "s0")
872+
testPath2Path("i1.e3.s0", { $0.popIfMatches(.enumCase, index: 3) }, expect: nil)
873+
testPath2Path("i*.e3.s0", { $0.popIfMatches(.indexedElement, index: 0) }, expect: "i*.e3.s0")
740874
}
741875

742876
func testPath2Path(_ pathStr: String, _ transform: (SmallProjectionPath) -> SmallProjectionPath?, expect: String?) {

0 commit comments

Comments
 (0)