Skip to content

Commit 12e2eb8

Browse files
authored
Merge pull request #2121 from ahoppen/ahoppen/syntax-visitor-visitation-function
Use two-step dispatch to `SyntaxVisitor.vistImpl`
2 parents c8faff7 + cfe2a11 commit 12e2eb8

File tree

6 files changed

+1529
-1679
lines changed

6 files changed

+1529
-1679
lines changed

CodeGeneration/Sources/Utils/CodeGenerationFormat.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import SwiftSyntax
1515

1616
/// A format style for files generated by CodeGeneration.
1717
public class CodeGenerationFormat: BasicFormat {
18-
public init() {
18+
/// The maximum number of array/dictionary/function parameters/tuple elements
19+
/// that should be put on the same line.
20+
private let maxElementsOnSameLine: Int
21+
22+
public init(maxElementsOnSameLine: Int = 3) {
23+
self.maxElementsOnSameLine = maxElementsOnSameLine
1924
super.init(indentationWidth: .spaces(2))
2025
}
2126

@@ -26,7 +31,7 @@ public class CodeGenerationFormat: BasicFormat {
2631
public override func visit(_ node: ArrayElementListSyntax) -> ArrayElementListSyntax {
2732
let children = node.children(viewMode: .all)
2833
// Short array literals are presented on one line, list each element on a different line.
29-
if children.count > 3 {
34+
if children.count > maxElementsOnSameLine {
3035
return ArrayElementListSyntax(formatChildrenSeparatedByNewline(children: children, elementType: ArrayElementSyntax.self))
3136
} else {
3237
return super.visit(node)
@@ -45,7 +50,7 @@ public class CodeGenerationFormat: BasicFormat {
4550
public override func visit(_ node: DictionaryElementListSyntax) -> DictionaryElementListSyntax {
4651
let children = node.children(viewMode: .all)
4752
// Short dictionary literals are presented on one line, list each element on a different line.
48-
if children.count > 3 {
53+
if children.count > maxElementsOnSameLine {
4954
return DictionaryElementListSyntax(formatChildrenSeparatedByNewline(children: children, elementType: DictionaryElementSyntax.self))
5055
} else {
5156
return super.visit(node)
@@ -55,7 +60,7 @@ public class CodeGenerationFormat: BasicFormat {
5560
public override func visit(_ node: FunctionParameterListSyntax) -> FunctionParameterListSyntax {
5661
let children = node.children(viewMode: .all)
5762
// Short function parameter literals are presented on one line, list each element on a different line.
58-
if children.count > 3 {
63+
if children.count > maxElementsOnSameLine {
5964
return FunctionParameterListSyntax(formatChildrenSeparatedByNewline(children: children, elementType: FunctionParameterSyntax.self))
6065
} else {
6166
return super.visit(node)
@@ -82,7 +87,7 @@ public class CodeGenerationFormat: BasicFormat {
8287
public override func visit(_ node: LabeledExprListSyntax) -> LabeledExprListSyntax {
8388
let children = node.children(viewMode: .all)
8489
// Short tuple element list literals are presented on one line, list each element on a different line.
85-
if children.count > 3 {
90+
if children.count > maxElementsOnSameLine {
8691
return LabeledExprListSyntax(formatChildrenSeparatedByNewline(children: children, elementType: LabeledExprSyntax.self))
8792
} else {
8893
return super.visit(node)

CodeGeneration/Sources/generate-swift-syntax/GenerateSwiftSyntax.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ struct GeneratedFileSpec {
4747
self.contentsGenerator = contents
4848
}
4949

50-
init(_ pathComponents: [String], _ contents: @escaping @autoclosure () -> SourceFileSyntax) {
51-
self.init(pathComponents, "\(contents().formatted(using: CodeGenerationFormat()))\n")
50+
init(_ pathComponents: [String], _ contents: @escaping @autoclosure () -> SourceFileSyntax, format: CodeGenerationFormat = CodeGenerationFormat()) {
51+
self.init(pathComponents, "\(contents().formatted(using: format))\n")
5252
}
5353
}
5454

@@ -112,7 +112,7 @@ struct GenerateSwiftSyntax: ParsableCommand {
112112
GeneratedFileSpec(swiftSyntaxGeneratedDir + ["SyntaxRewriter.swift"], syntaxRewriterFile),
113113
GeneratedFileSpec(swiftSyntaxGeneratedDir + ["SyntaxTraits.swift"], syntaxTraitsFile),
114114
GeneratedFileSpec(swiftSyntaxGeneratedDir + ["SyntaxTransform.swift"], syntaxTransformFile),
115-
GeneratedFileSpec(swiftSyntaxGeneratedDir + ["SyntaxVisitor.swift"], syntaxVisitorFile),
115+
GeneratedFileSpec(swiftSyntaxGeneratedDir + ["SyntaxVisitor.swift"], syntaxVisitorFile, format: CodeGenerationFormat(maxElementsOnSameLine: 4)),
116116
GeneratedFileSpec(swiftSyntaxGeneratedDir + ["TokenKind.swift"], tokenKindFile),
117117
GeneratedFileSpec(swiftSyntaxGeneratedDir + ["Tokens.swift"], tokensFile),
118118
GeneratedFileSpec(swiftSyntaxGeneratedDir + ["TriviaPieces.swift"], triviaPiecesFile),

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
210210
/// we need to switch through a huge switch statement that covers all syntax
211211
/// types. In debug builds, the cases of this switch statement do not share
212212
/// stack space (rdar://55929175). Because of this, the switch statement
213-
/// requires allocates about 15KB of stack space. In scenarios with reduced
213+
/// requires about 15KB of stack space. In scenarios with reduced
214214
/// stack size (in particular dispatch queues), this often results in a stack
215215
/// overflow during syntax tree rewriting.
216216
///

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxVisitorFile.swift

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -113,27 +113,108 @@ let syntaxVisitorFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
113113
"""
114114
)
115115

116-
try FunctionDeclSyntax("private func visit(_ data: SyntaxData)") {
117-
try SwitchExprSyntax("switch data.raw.kind") {
118-
SwitchCaseSyntax("case .token:") {
119-
DeclSyntax("let node = TokenSyntax(data)")
120-
121-
ExprSyntax("_ = visit(node)")
122-
ExprSyntax(
123-
"""
124-
// No children to visit.
125-
visitPost(node)
126-
"""
116+
try IfConfigDeclSyntax(
117+
leadingTrivia:
118+
"""
119+
// SwiftSyntax requires a lot of stack space in debug builds for syntax tree
120+
// visitation. In scenarios with reduced stack space (in particular dispatch
121+
// queues), this easily results in a stack overflow. To work around this issue,
122+
// use a less performant but also less stack-hungry version of SwiftSyntax's
123+
// SyntaxVisitor in debug builds.
124+
125+
""",
126+
clauses: IfConfigClauseListSyntax {
127+
IfConfigClauseSyntax(
128+
poundKeyword: .poundIfToken(),
129+
condition: ExprSyntax("DEBUG"),
130+
elements: .statements(
131+
try CodeBlockItemListSyntax {
132+
try FunctionDeclSyntax(
133+
"""
134+
/// Implementation detail of visit(_:). Do not call directly.
135+
///
136+
/// Returns the function that shall be called to visit a specific syntax node.
137+
///
138+
/// To determine the correct specific visitation function for a syntax node,
139+
/// we need to switch through a huge switch statement that covers all syntax
140+
/// types. In debug builds, the cases of this switch statement do not share
141+
/// stack space (rdar://55929175). Because of this, the switch statement
142+
/// requires about 15KB of stack space. In scenarios with reduced
143+
/// stack size (in particular dispatch queues), this often results in a stack
144+
/// overflow during syntax tree rewriting.
145+
///
146+
/// To circumvent this problem, make calling the specific visitation function
147+
/// a two-step process: First determine the function to call in this function
148+
/// and return a reference to it, then call it. This way, the stack frame
149+
/// that determines the correct visitation function will be popped of the
150+
/// stack before the function is being called, making the switch's stack
151+
/// space transient instead of having it linger in the call stack.
152+
private func visitationFunc(for data: SyntaxData) -> ((SyntaxData) -> Void)
153+
"""
154+
) {
155+
try SwitchExprSyntax("switch data.raw.kind") {
156+
SwitchCaseSyntax("case .token:") {
157+
StmtSyntax(
158+
"""
159+
return {
160+
let node = TokenSyntax($0)
161+
_ = self.visit(node)
162+
// No children to visit.
163+
self.visitPost(node)
164+
}
165+
"""
166+
)
167+
}
168+
169+
for node in NON_BASE_SYNTAX_NODES {
170+
SwitchCaseSyntax("case .\(node.varOrCaseName):") {
171+
StmtSyntax("return { self.visitImpl($0, \(node.kind.syntaxType).self, self.visit, self.visitPost) }")
172+
}
173+
}
174+
}
175+
}
176+
177+
DeclSyntax(
178+
"""
179+
private func visit(_ data: SyntaxData) {
180+
return visitationFunc(for: data)(data)
181+
}
182+
"""
183+
)
184+
}
127185
)
128-
}
186+
)
187+
IfConfigClauseSyntax(
188+
poundKeyword: .poundElseToken(),
189+
elements: .statements(
190+
CodeBlockItemListSyntax {
191+
try! FunctionDeclSyntax("private func visit(_ data: SyntaxData)") {
192+
try SwitchExprSyntax("switch data.raw.kind") {
193+
SwitchCaseSyntax("case .token:") {
194+
DeclSyntax("let node = TokenSyntax(data)")
129195

130-
for node in NON_BASE_SYNTAX_NODES {
131-
SwitchCaseSyntax("case .\(node.varOrCaseName):") {
132-
ExprSyntax("visitImpl(data, \(node.kind.syntaxType).self, visit, visitPost)")
133-
}
134-
}
196+
ExprSyntax("_ = visit(node)")
197+
ExprSyntax(
198+
"""
199+
// No children to visit.
200+
visitPost(node)
201+
"""
202+
)
203+
}
204+
205+
for node in NON_BASE_SYNTAX_NODES {
206+
SwitchCaseSyntax("case .\(node.varOrCaseName):") {
207+
ExprSyntax("visitImpl(data, \(node.kind.syntaxType).self, visit, visitPost)")
208+
}
209+
}
210+
}
211+
}
212+
213+
}
214+
)
215+
)
135216
}
136-
}
217+
)
137218

138219
DeclSyntax(
139220
"""

Sources/SwiftSyntax/generated/SyntaxRewriter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2073,7 +2073,7 @@ open class SyntaxRewriter {
20732073
/// we need to switch through a huge switch statement that covers all syntax
20742074
/// types. In debug builds, the cases of this switch statement do not share
20752075
/// stack space (rdar://55929175). Because of this, the switch statement
2076-
/// requires allocates about 15KB of stack space. In scenarios with reduced
2076+
/// requires about 15KB of stack space. In scenarios with reduced
20772077
/// stack size (in particular dispatch queues), this often results in a stack
20782078
/// overflow during syntax tree rewriting.
20792079
///

0 commit comments

Comments
 (0)