Skip to content

Commit a9eab70

Browse files
authored
Merge pull request #1257 from ahoppen/ahoppen/performance-improvements
Fix performance regressions introduced by my recent changes
2 parents 551ffd4 + 8d56221 commit a9eab70

23 files changed

+855
-369
lines changed

CodeGeneration/Sources/generate-swiftsyntax/KeywordFile.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ import SwiftSyntaxBuilder
1515
import SyntaxSupport
1616
import Utils
1717

18+
let lookupTable = ArrayExprSyntax {
19+
for keyword in KEYWORDS {
20+
ArrayElementSyntax(expression: ExprSyntax("\(literal: keyword.name)"))
21+
}
22+
}
23+
1824
let keywordFile = SourceFileSyntax {
1925
ExtensionDeclSyntax(
2026
"""
@@ -31,10 +37,10 @@ let keywordFile = SourceFileSyntax {
3137

3238
EnumDeclSyntax("""
3339
@frozen // FIXME: Not actually stable, works around a miscompile
34-
public enum Keyword: StaticString
40+
public enum Keyword: UInt8, Hashable
3541
""") {
36-
for keyword in KEYWORDS {
37-
EnumCaseDeclSyntax("case \(raw: keyword.escapedName)")
42+
for (index, keyword) in KEYWORDS.enumerated() {
43+
EnumCaseDeclSyntax("case \(raw: keyword.escapedName) = \(literal: index)")
3844
}
3945

4046
InitializerDeclSyntax("@_spi(RawSyntax) public init?(_ text: SyntaxText)") {
@@ -75,10 +81,20 @@ let keywordFile = SourceFileSyntax {
7581
}
7682
}
7783

84+
VariableDeclSyntax("""
85+
/// This is really unfortunate. Really, we should have a `switch` in
86+
/// `Keyword.defaultText` to return the keyword's kind but the constant lookup
87+
/// table is significantly faster. Ideally, we could also get the compiler to
88+
/// constant-evaluate `Keyword.spi.defaultText` to a `SyntaxText` but I don't
89+
/// see how that's possible right now.
90+
private static let keywordTextLookupTable: [SyntaxText] = \(lookupTable)
91+
""")
92+
7893
VariableDeclSyntax(
7994
"""
80-
var defaultText: SyntaxText {
81-
return SyntaxText(self.rawValue)
95+
@_spi(RawSyntax)
96+
public var defaultText: SyntaxText {
97+
return Keyword.keywordTextLookupTable[Int(self.rawValue)]
8298
}
8399
"""
84100
)

CodeGeneration/Sources/generate-swiftsyntax/templates/ideutils/SyntaxClassificationFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ let syntaxClassificationFile = SourceFileSyntax {
9494
modifiers: [DeclModifierSyntax(name: .keyword(.internal))],
9595
name: IdentifierPatternSyntax("classification"),
9696
type: TypeAnnotationSyntax(type: TypeSyntax("SyntaxClassification"))) {
97-
SwitchStmtSyntax(expression: ExprSyntax("self")) {
97+
SwitchStmtSyntax(expression: ExprSyntax("self.base")) {
9898
for token in SYNTAX_TOKENS {
9999
SwitchCaseSyntax("case .\(raw: token.swiftKind):") {
100100
if let classification = token.classification {

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/TokenKindFile.swift

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -203,35 +203,98 @@ let tokenKindFile = SourceFileSyntax {
203203
}
204204

205205
EnumDeclSyntax("""
206-
/// Plain token kind value, without an associated `String` value.
206+
// Note: It's important that this enum is marked as having a raw base kind
207+
// because it significantly improves performance when comparing two
208+
// `RawTokenBaseKind` for equality. With the raw value, it compiles down to
209+
// a primitive integer compare, without, it calls into `__derived_enum_equals`.
207210
@frozen // FIXME: Not actually stable, works around a miscompile
208-
public enum RawTokenKind: Equatable, Hashable
211+
public enum RawTokenBaseKind: UInt8, Equatable, Hashable
209212
""") {
210213
EnumCaseDeclSyntax("case eof")
211214

212215
for token in SYNTAX_TOKENS {
213-
if let associatedValueClass = token.associatedValueClass {
214-
EnumCaseDeclSyntax("case \(raw: token.swiftKind)(\(raw: associatedValueClass))")
215-
} else {
216-
EnumCaseDeclSyntax("case \(raw: token.swiftKind)")
216+
EnumCaseDeclSyntax("case \(raw: token.swiftKind)")
217+
}
218+
}
219+
220+
DeclSyntax("""
221+
fileprivate extension Keyword {
222+
static var rawValueZero: Keyword {
223+
return Keyword(rawValue: 0)!
224+
}
225+
}
226+
""")
227+
228+
StructDeclSyntax("""
229+
/// Similar to `TokenKind` but without a `String` associated value.
230+
/// Technically, this should be an enum like
231+
/// ```
232+
/// enum RawTokenKind {
233+
/// case eof
234+
/// case associatedtypeKeyword
235+
/// // remaining case from `RawTokenBaseKind`...
236+
/// case keyword(Keyword)
237+
/// }
238+
/// ```
239+
///
240+
/// But modelling it this way has significant performance implications since
241+
/// comparing two `RawTokenKind` calls into `__derived_enum_equals`. It's more
242+
/// effient to model the base kind as an enum with a raw value and store the
243+
/// keyword separately.
244+
///
245+
/// Whenever `base` is not `keyword`, `keyword` should have a raw value
246+
/// of `0`.
247+
@frozen // FIXME: Not actually stable, works around a miscompile
248+
public struct RawTokenKind: Equatable, Hashable
249+
""") {
250+
DeclSyntax("public let base: RawTokenBaseKind")
251+
DeclSyntax("public let keyword: Keyword")
252+
253+
DeclSyntax("""
254+
public init(base: RawTokenBaseKind, keyword: Keyword) {
255+
assert(base == .keyword || keyword.rawValue == 0)
256+
self.base = base
257+
self.keyword = keyword
217258
}
259+
"""
260+
)
261+
262+
DeclSyntax("""
263+
public static var eof: RawTokenKind {
264+
return RawTokenKind(base: .eof, keyword: .rawValueZero)
265+
}
266+
""")
267+
for token in SYNTAX_TOKENS where token.swiftKind != "keyword" {
268+
VariableDeclSyntax(
269+
modifiers: [DeclModifierSyntax(leadingTrivia: .newline, name: .keyword(.public)), DeclModifierSyntax(name: .keyword(.static))],
270+
name: IdentifierPatternSyntax("\(raw: token.swiftKind)"),
271+
type: TypeAnnotationSyntax(type: TypeSyntax("RawTokenKind"))
272+
) {
273+
StmtSyntax("return RawTokenKind(base: .\(raw: token.swiftKind), keyword: .rawValueZero)")
274+
}
275+
}
276+
277+
DeclSyntax("""
278+
public static func keyword(_ keyword: Keyword) -> RawTokenKind {
279+
return RawTokenKind(base: .keyword, keyword: keyword)
218280
}
281+
""")
219282

220283
VariableDeclSyntax(
221284
attributes: [.attribute(AttributeSyntax(attributeName: TypeSyntax("_spi"), leftParen: .leftParenToken(), argument: .token(.identifier("RawSyntax")), rightParen: .rightParenToken()))],
222285
modifiers: [DeclModifierSyntax(leadingTrivia: .newline, name: .keyword(.public))],
223286
name: IdentifierPatternSyntax("defaultText"),
224287
type: TypeAnnotationSyntax(type: OptionalTypeSyntax("SyntaxText?"))
225288
) {
226-
SwitchStmtSyntax(expression: ExprSyntax("self")) {
289+
SwitchStmtSyntax(expression: ExprSyntax("self.base")) {
227290
SwitchCaseSyntax("case .eof:") {
228291
ReturnStmtSyntax(#"return """#)
229292
}
230293

231294
for token in SYNTAX_TOKENS {
232-
if token.associatedValueClass != nil {
233-
SwitchCaseSyntax("case .\(raw: token.swiftKind)(let assoc):") {
234-
ReturnStmtSyntax("return assoc.defaultText")
295+
if token.swiftKind == "keyword" {
296+
SwitchCaseSyntax("case .\(raw: token.swiftKind):") {
297+
ReturnStmtSyntax("return self.keyword.defaultText")
235298
}
236299
} else if let text = token.text {
237300
SwitchCaseSyntax("case .\(raw: token.swiftKind):") {
@@ -251,7 +314,7 @@ let tokenKindFile = SourceFileSyntax {
251314
name: IdentifierPatternSyntax("nameForDiagnostics"),
252315
type: TypeAnnotationSyntax(type: TypeSyntax("String"))
253316
) {
254-
SwitchStmtSyntax(expression: ExprSyntax("self")) {
317+
SwitchStmtSyntax(expression: ExprSyntax("self.base")) {
255318
SwitchCaseSyntax("case .eof:") {
256319
ReturnStmtSyntax(#"return "end of file""#)
257320
}
@@ -261,12 +324,12 @@ let tokenKindFile = SourceFileSyntax {
261324
ReturnStmtSyntax("return #\"\(raw: token.nameForDiagnostics)\"#")
262325
}
263326
}
264-
SwitchCaseSyntax("case .keyword(let keyword):") {
265-
ReturnStmtSyntax("return String(syntaxText: keyword.defaultText)")
327+
SwitchCaseSyntax("case .keyword:") {
328+
ReturnStmtSyntax("return String(syntaxText: self.keyword.defaultText)")
266329
}
267330
}
268331
}
269-
332+
270333
VariableDeclSyntax(
271334
leadingTrivia: [
272335
.docBlockComment("/// Returns `true` if the token is a Swift keyword."),
@@ -284,7 +347,7 @@ let tokenKindFile = SourceFileSyntax {
284347
name: IdentifierPatternSyntax("isLexerClassifiedKeyword"),
285348
type: TypeAnnotationSyntax(type: TypeSyntax("Bool"))
286349
) {
287-
SwitchStmtSyntax(expression: ExprSyntax("self")) {
350+
SwitchStmtSyntax(expression: ExprSyntax("self.base")) {
288351
SwitchCaseSyntax("case .eof:") {
289352
ReturnStmtSyntax("return false")
290353
}
@@ -294,8 +357,8 @@ let tokenKindFile = SourceFileSyntax {
294357
ReturnStmtSyntax("return \(raw: token.isKeyword)")
295358
}
296359
}
297-
SwitchCaseSyntax("case .keyword(let keyword):") {
298-
ReturnStmtSyntax("return keyword.isLexerClassified")
360+
SwitchCaseSyntax("case .keyword:") {
361+
ReturnStmtSyntax("return self.keyword.isLexerClassified")
299362
}
300363
}
301364
}
@@ -317,7 +380,7 @@ let tokenKindFile = SourceFileSyntax {
317380
name: IdentifierPatternSyntax("isPunctuation"),
318381
type: TypeAnnotationSyntax(type: TypeSyntax("Bool"))
319382
) {
320-
SwitchStmtSyntax(expression: ExprSyntax("self")) {
383+
SwitchStmtSyntax(expression: ExprSyntax("self.base")) {
321384
SwitchCaseSyntax("case .eof:") {
322385
ReturnStmtSyntax("return false")
323386
}
@@ -370,16 +433,16 @@ let tokenKindFile = SourceFileSyntax {
370433
@_spi(RawSyntax)
371434
public static func fromRaw(kind rawKind: RawTokenKind, text: String) -> TokenKind
372435
""") {
373-
SwitchStmtSyntax(expression: ExprSyntax("rawKind")) {
436+
SwitchStmtSyntax(expression: ExprSyntax("rawKind.base")) {
374437
SwitchCaseSyntax("case .eof:") {
375438
ReturnStmtSyntax("return .eof")
376439
}
377440

378441
for token in SYNTAX_TOKENS {
379-
if token.associatedValueClass != nil {
380-
SwitchCaseSyntax("case .\(raw: token.swiftKind)(let assoc):") {
381-
FunctionCallExprSyntax("assert(text.isEmpty || String(syntaxText: assoc.defaultText) == text)")
382-
ReturnStmtSyntax("return .\(raw: token.swiftKind)(assoc)")
442+
if token.swiftKind == "keyword" {
443+
SwitchCaseSyntax("case .\(raw: token.swiftKind):") {
444+
FunctionCallExprSyntax("assert(text.isEmpty || String(syntaxText: rawKind.keyword.defaultText) == text)")
445+
ReturnStmtSyntax("return .keyword(rawKind.keyword)")
383446
}
384447
} else if token.text != nil {
385448
SwitchCaseSyntax("case .\(raw: token.swiftKind):") {
@@ -407,9 +470,9 @@ let tokenKindFile = SourceFileSyntax {
407470
}
408471

409472
for token in SYNTAX_TOKENS {
410-
if token.associatedValueClass != nil {
411-
SwitchCaseSyntax("case .\(raw: token.swiftKind)(let assoc):") {
412-
ReturnStmtSyntax("return (.\(raw: token.swiftKind)(assoc), nil)")
473+
if token.swiftKind == "keyword" {
474+
SwitchCaseSyntax("case .\(raw: token.swiftKind)(let keyword):") {
475+
ReturnStmtSyntax("return (.\(raw: token.swiftKind)(keyword), nil)")
413476
}
414477
} else if token.text != nil {
415478
SwitchCaseSyntax("case .\(raw: token.swiftKind):") {

Sources/IDEUtils/generated/SyntaxClassification.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ extension SyntaxClassification {
137137

138138
extension RawTokenKind {
139139
internal var classification: SyntaxClassification {
140-
switch self {
140+
switch self.base {
141141
case .wildcard:
142142
return .none
143143
case .leftParen:

Sources/SwiftParser/Attributes.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ extension Parser {
170170
case .required:
171171
shouldParseArgument = true
172172
case .customAttribute:
173-
shouldParseArgument = self.lookahead().isCustomAttributeArgument() && self.at(.leftParen, allowTokenAtStartOfLine: false)
173+
shouldParseArgument = self.withLookahead { $0.isCustomAttributeArgument() } && self.at(.leftParen, allowTokenAtStartOfLine: false)
174174
case .optional:
175175
shouldParseArgument = self.at(.leftParen)
176176
}
@@ -1113,7 +1113,7 @@ extension Parser {
11131113
// MARK: Lookahead
11141114

11151115
extension Parser.Lookahead {
1116-
func isCustomAttributeArgument() -> Bool {
1116+
mutating func isCustomAttributeArgument() -> Bool {
11171117
var lookahead = self.lookahead()
11181118
lookahead.skipSingle()
11191119

@@ -1146,7 +1146,7 @@ extension Parser.Lookahead {
11461146
return false
11471147
}
11481148

1149-
if self.at(.leftParen, allowTokenAtStartOfLine: false) && self.lookahead().isCustomAttributeArgument() {
1149+
if self.at(.leftParen, allowTokenAtStartOfLine: false) && self.withLookahead({ $0.isCustomAttributeArgument() }) {
11501150
self.skipSingle()
11511151
}
11521152

Sources/SwiftParser/Declarations.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extension DeclarationModifier {
2929
}
3030

3131
extension TokenConsumer {
32-
func atStartOfDeclaration(
32+
mutating func atStartOfDeclaration(
3333
isAtTopLevel: Bool = false,
3434
allowInitDecl: Bool = true,
3535
allowRecovery: Bool = false
@@ -1242,7 +1242,7 @@ extension Parser {
12421242

12431243
extension Parser {
12441244
/// Are we at a regular expression literal that could act as an operator?
1245-
private func atRegexLiteralThatCouldBeAnOperator() -> Bool {
1245+
private mutating func atRegexLiteralThatCouldBeAnOperator() -> Bool {
12461246
guard self.at(.regexLiteral) else {
12471247
return false
12481248
}
@@ -2258,7 +2258,7 @@ extension Parser {
22582258

22592259
// Parse the optional generic argument list.
22602260
let generics: RawGenericArgumentClauseSyntax?
2261-
if self.lookahead().canParseAsGenericArgumentList() {
2261+
if self.withLookahead({ $0.canParseAsGenericArgumentList() }) {
22622262
generics = self.parseGenericArguments()
22632263
} else {
22642264
generics = nil
@@ -2282,7 +2282,7 @@ extension Parser {
22822282
let trailingClosure: RawClosureExprSyntax?
22832283
let additionalTrailingClosures: RawMultipleTrailingClosureElementListSyntax?
22842284
if self.at(.leftBrace),
2285-
self.lookahead().isValidTrailingClosure(.trailingClosure)
2285+
self.withLookahead({ $0.isValidTrailingClosure(.trailingClosure) })
22862286
{
22872287
(trailingClosure, additionalTrailingClosures) =
22882288
self.parseTrailingClosures(.trailingClosure)

0 commit comments

Comments
 (0)