@@ -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 """
0 commit comments