-
Notifications
You must be signed in to change notification settings - Fork 65
/
TCaseStmtNode.class.st
439 lines (373 loc) · 15 KB
/
TCaseStmtNode.class.st
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
"
I implement the main dispatch case statements for bytecode and primitive dispatch. See TMethod classPool associationAt: #CaseStatements
"
Class {
#name : #TCaseStmtNode,
#superclass : #TParseNode,
#instVars : [
'expression',
'firsts',
'lasts',
'cases'
],
#category : #'Slang-AST'
}
{ #category : #'instance-creation' }
TCaseStmtNode class >> newWithExpression: anExpression selectors: aCollectionOfSelectors arguments: arguments [
^ self new
setExpression: anExpression
selectors: aCollectionOfSelectors
arguments: arguments;
yourself
]
{ #category : #visiting }
TCaseStmtNode >> accept: aVisitor [
^ aVisitor visitCaseStatementNode: self
]
{ #category : #tranforming }
TCaseStmtNode >> asCASTIn: aBuilder [
| statements expansions duplicates switchStatement result |
statements := CCompoundStatementNode new.
expansions := aBuilder suppressAsmLabelsWhile: [
cases collect: [:case| self filterCommentsFrom:
(String streamContents: [:s| (case asCASTIn: aBuilder) prettyPrintOn: s ] ) ] ].
duplicates := Set new.
1 to: cases size do: [:i| | case lastCase |
"If case bodies are the same, keep only one."
(duplicates includes: i) ifFalse: [
"Cases are nested.
We iterate from the last case to the first case so the nesting order remains the same as the defined one"
(duplicates addAll: ((cases size to: i by: -1) select: [:j| (expansions at: i) = (expansions at: j)])) do: [:k|
(lasts at: k) to: (firsts at: k) by: -1 do: [ :caseIndex |
case := CLabeledStatementNode case: (CConstantNode value: caseIndex) statement: case.
"Remember the last case, will be the one attached to the statement"
lastCase ifNil: [ lastCase := case ].
]].
lastCase statement: ((cases at: i) asCASTIn: aBuilder).
statements add: case.
statements add: CBreakStatementNode new]].
switchStatement := CSwitchStatementNode
if: (expression asCASTExpressionIn: aBuilder)
statement: statements.
(expression isVariable and: [expression name = 'currentBytecode']) ifFalse: [ ^ switchStatement ].
result := CCompoundStatementNode new.
result needsBrackets: false.
result add: (CCallNode identifier: (CIdentifierNode name: 'bytecodeDispatchDebugHook')).
result add: (aBuilder asmLabelNodeFor: 'bytecodeDispatch').
result add: switchStatement.
^ result
]
{ #category : #tranforming }
TCaseStmtNode >> asCASTIn: aBuilder addToEndOfCases: aNodeToPrepend [
| statements |
statements := CCompoundStatementNode new.
cases withIndexDo: [:case :i| | ccase lastCase |
"If case bodies are the same, keep only one."
"Cases are nested.
We iterate from the last case to the first case so the nesting order remains the same as the defined one"
(lasts at: i) to: (firsts at: i) by: -1 do: [ :caseIndex |
ccase := CLabeledStatementNode case: (CConstantNode value: caseIndex) statement: ccase.
"Remember the last case, will be the one attached to the statement"
lastCase ifNil: [ lastCase := ccase ].
].
lastCase statement: (case asCASTIn: aBuilder prependToEnd: aNodeToPrepend).
statements add: ccase.
(aNodeToPrepend notNil and: [aNodeToPrepend isReturn]) ifFalse: [
statements add: CBreakStatementNode new ]
].
statements add: (CLabeledStatementNode defaultDoing: ((CCompoundStatementNode statements: {
CCallNode
identifier: (CIdentifierNode name: 'error')
arguments: { CStringLiteralNode value: 'Case not found' }.
CReturnStatementNode expression: (CConstantNode value: -1).
}) needsBrackets: false; yourself)).
^ CSwitchStatementNode
if: (expression asCASTExpressionIn: aBuilder)
statement: statements
]
{ #category : #transformations }
TCaseStmtNode >> bindVariableUsesIn: aDictionary [
self expression: (expression bindVariableUsesIn: aDictionary).
self cases: (cases collect: [ :c | c bindVariableUsesIn: aDictionary ]).
]
{ #category : #transformations }
TCaseStmtNode >> bindVariableUsesIn: aDictionary andConstantFoldIf: constantFold in: codeGen [
"Answer either the receiver, if it contains no references to the given variables, or a new node with the given variables rebound."
| newExpression newCases |
newExpression := expression bindVariableUsesIn: aDictionary andConstantFoldIf: constantFold in: codeGen.
newCases := cases collect: [:c| c bindVariableUsesIn: aDictionary andConstantFoldIf: constantFold in: codeGen].
^(newExpression = expression
and: [newCases = cases])
ifTrue: [self]
ifFalse: [self shallowCopy
setExpression: newExpression cases: newCases;
yourself]
]
{ #category : #transformations }
TCaseStmtNode >> bindVariablesIn: aDictionary [
self expression: (expression bindVariablesIn: aDictionary).
self cases: (cases collect: [ :c | c bindVariablesIn: aDictionary ]).
]
{ #category : #transformations }
TCaseStmtNode >> bindVariablesIn: aDictionary unless: cautionaryBlock [
(cautionaryBlock value: self) ifTrue: [^self].
self expression: (expression bindVariablesIn: aDictionary unless: cautionaryBlock).
self cases: (cases collect: [ :c | c bindVariablesIn: aDictionary unless: cautionaryBlock ]).
]
{ #category : #comparing }
TCaseStmtNode >> cases [
^ cases
]
{ #category : #comparing }
TCaseStmtNode >> cases: aListOfCases [
cases := aListOfCases.
cases do: [ :e | e parent: self ]
]
{ #category : #accessing }
TCaseStmtNode >> children [
^ { expression }, cases
]
{ #category : #transformations }
TCaseStmtNode >> customizeCase: caseParseTree forVar: varName from: firstIndex to: lastIndex in: codeGen method: aTMethod expandedCases: seen [
"Return a collection of copies of the given parse tree, each of which has the value of the case index substituted for the given variable."
| newCases dict newCase |
newCases := OrderedCollection new.
firstIndex to: lastIndex do:
[ :caseIndex |
dict := Dictionary new.
dict at: varName put: (TConstantNode value: caseIndex).
newCase := caseParseTree copy bindVariableUsesIn: dict andConstantFoldIf: true in: codeGen.
self processSharedCodeBlocks: newCase forCase: caseIndex in: codeGen method: aTMethod expandedCases: seen.
newCases addLast: newCase].
^newCases
]
{ #category : #transformations }
TCaseStmtNode >> customizeShortCasesForDispatchVar: varName in: codeGen method: aTMethod [
"Make customized versions of short bytecode methods, substituting a constant having the case index value for the given variable.
This produces better code for short bytecodes such as instance variable pushes that encode the index of the instance variable in the bytecode."
| newFirsts newLasts newCases seen |
newFirsts := OrderedCollection new.
newLasts := OrderedCollection new.
newCases := OrderedCollection new.
seen := Set new. "So that sharedCodeNamed:inCase:'s can be used in multiple bytecode set
configurations we need to expand the code only once."
1 to: cases size do:
[ :i | | l f case expanded |
l := lasts at: i.
f := firsts at: i.
case := cases at: i.
expanded := false.
((l - f) > 1 "case code covers multiple cases"
and: [case shouldExpand]) ifTrue:
[newFirsts addAll: (f to: l) asArray.
newLasts addAll: (f to: l) asArray.
newCases addAll: (self customizeCase: case forVar: varName from: f to: l in: codeGen method: aTMethod expandedCases: seen).
expanded := true].
expanded ifFalse:
[self processSharedCodeBlocks: case forCase: f in: codeGen method: aTMethod expandedCases: seen.
newFirsts addLast: f.
newLasts addLast: l.
newCases addLast: case]].
firsts := newFirsts asArray.
lasts := newLasts asArray.
self cases: newCases asArray
]
{ #category : #accessing }
TCaseStmtNode >> expression [
^ expression
]
{ #category : #accessing }
TCaseStmtNode >> expression: anExpression [
expression := anExpression.
anExpression parent: self
]
{ #category : #private }
TCaseStmtNode >> filterCommentsFrom: aString [
"elide any /* comment */ occurrences from aString."
| i |
i := aString indexOfSubCollection: '/*'startingAt: 1 ifAbsent: [^aString].
^(aString copyFrom: 1 to: i - 1),
(self filterCommentsFrom:
(aString
copyFrom: (aString indexOfSubCollection: '*/' startingAt: i + 2) + 2
to: aString size))
]
{ #category : #testing }
TCaseStmtNode >> isCaseStmt [
^true
]
{ #category : #comparing }
TCaseStmtNode >> isSameAs: anotherNode [
anotherNode isCaseStmt ifFalse: [ ^ false ].
(expression isSameAs: anotherNode expression)
ifFalse: [ ^ false ].
cases size ~= anotherNode cases size ifTrue: [ ^ false ].
cases with: anotherNode cases collect: [ :case1 :case2 |
(case1 isSameAs: case2)
].
"Apparently it went ok after all this"
^ true
]
{ #category : #enumerating }
TCaseStmtNode >> nodesDo: aBlock [
"Apply aBlock to all nodes in the receiver.
N.B. This is assumed to be bottom-up, leaves first."
expression nodesDo: aBlock.
cases do: [ :c | c nodesDo: aBlock ].
aBlock value: self
]
{ #category : #enumerating }
TCaseStmtNode >> nodesDo: aBlock parent: parent [
"Apply aBlock to all nodes in the receiver with each node's parent.
N.B. This is assumed to be bottom-up, leaves first."
expression nodesDo: aBlock parent: self.
cases do: [:c| c nodesDo: aBlock parent: self].
aBlock value: self value: parent
]
{ #category : #enumerating }
TCaseStmtNode >> nodesDo: aBlock parent: parent unless: cautionaryBlock [
(cautionaryBlock value: self value: parent) ifTrue: [^self].
expression nodesDo: aBlock parent: self unless: cautionaryBlock.
cases do: [ :c | c nodesDo: aBlock parent: self unless: cautionaryBlock].
aBlock value: self value: parent
]
{ #category : #enumerating }
TCaseStmtNode >> nodesDo: aBlock unless: cautionaryBlock [
(cautionaryBlock value: self) ifTrue: [^self].
expression nodesDo: aBlock unless: cautionaryBlock.
cases do: [ :c | c nodesDo: aBlock unless: cautionaryBlock].
aBlock value: self
]
{ #category : #copying }
TCaseStmtNode >> postCopy [
self expression: expression copy.
firsts := firsts copy.
lasts := lasts copy.
self cases: (cases collect: [ :case | case copy ])
]
{ #category : #printing }
TCaseStmtNode >> printOn: aStream level: level [
aStream crtab: level.
aStream nextPutAll: 'select '.
expression printOn: aStream level: level.
aStream nextPutAll: ' in'.
1 to: cases size do: [ :i |
(firsts at: i) to: (lasts at: i) do: [ :caseIndex |
aStream crtab: level.
aStream nextPutAll: 'case ', caseIndex printString, ':'.
].
aStream crtab: level + 1.
(cases at: i) printOn: aStream level: level + 1.
].
aStream crtab: level.
aStream nextPutAll: 'end select'.
]
{ #category : #transformations }
TCaseStmtNode >> processSharedCodeBlocks: caseTree forCase: caseIndex in: codeGen method: aTMethod expandedCases: seen [
"Process any shared code blocks in the case parse tree for the given case, either inlining them or making them a 'goto sharedLabel'."
| caseMethod map meth sharedNode exitLabel |
exitLabel := nil.
"caseTree is expected to be a TStmtListNode whose first element is a comment
and whose second element is a TInlineNode for a method."
caseMethod := caseTree statements second method.
[
sharedNode := nil.
map := IdentityDictionary new.
caseTree nodesDo: [ :node |
(sharedNode isNil and: [
node isSend and: [
(meth := codeGen methodNamed: node selector) notNil and: [
meth sharedCase notNil ] ] ]) ifTrue: [
(meth sharedCase = (meth sharedCase isSymbol
ifTrue: [ caseMethod selector ]
ifFalse: [ caseIndex ]) and: [
(seen includes: meth sharedLabel) not ])
ifTrue: [ "If the bytecode (the caseMethod) ends with a message that has a lastCase (and lastLabel) then
that will be converted into a goto and control will continue to that code, If the bytecode does
/not/ end with a message that has a lastCase (and lastLabel) then control should not continue to
that shared case. expandViaFallThrough captures this, true for the former, false for the latter."
| expandViaFallThrough |
expandViaFallThrough := false.
caseMethod statements last isSend ifTrue: [
(codeGen methodNamed: caseMethod statements last selector)
ifNotNil: [ :m | expandViaFallThrough := m sharedCase notNil ] ].
seen add: meth sharedLabel.
map at: node put: (expandViaFallThrough
ifTrue: [
sharedNode := meth.
TLabeledCommentNode new setComment:
'goto ' , meth sharedLabel ]
ifFalse: [ "Still need recursive expansjon to continue but don't want
to duplicate the node, so substitue an empty method."
sharedNode := TLabeledCommentNode new setComment: 'null '.
meth copy
renameLabelsForInliningInto: aTMethod;
addLabelsTo: aTMethod;
asInlineNode ]) ]
ifFalse: [ map at: node put: (TGoToNode label: meth sharedLabel) ] ] ].
caseTree replaceNodesIn: map.
"recursively expand"
sharedNode notNil ] whileTrue: [
sharedNode isTMethod ifTrue: [
meth := sharedNode copy.
meth hasReturn ifTrue: [
exitLabel ifNil: [
exitLabel := aTMethod unusedLabelForInliningInto: aTMethod.
aTMethod labels add: exitLabel ].
meth exitVar: nil label: exitLabel ].
meth
renameLabelsForInliningInto: aTMethod;
addLabelsTo: aTMethod.
caseTree statements:
(caseTree statements copyWith: meth asInlineNode) ] ].
exitLabel ifNotNil: [
caseTree statements: (caseTree statements copyWith:
(TLabeledCommentNode new setLabel: exitLabel comment: 'end case')) ]
]
{ #category : #transformations }
TCaseStmtNode >> removeAssertions [
expression removeAssertions.
cases do: [ :case | case removeAssertions ].
]
{ #category : #transformations }
TCaseStmtNode >> replaceNodesIn: aDictionary [
^aDictionary at: self ifAbsent: [
self expression: (expression replaceNodesIn: aDictionary).
self cases: (cases collect: [ :c | c replaceNodesIn: aDictionary ]).
self]
]
{ #category : #private }
TCaseStmtNode >> setExpression: newExpression cases: newCases [
self expression: newExpression.
self cases: newCases
]
{ #category : #accessing }
TCaseStmtNode >> setExpression: aNode selectors: selectorList arguments: anArray [
"Initialize the node from the given set of selectors."
"Note: Each case is a statement list with containing one statement, a send to self of a selector from the given selector list. Having statement list nodes makes inlining easier later."
| selfNode stmt lastSel firstInRun sel case |
self expression: aNode.
selfNode := TVariableNode new setName: 'self'.
firsts := OrderedCollection new: 400.
lasts := OrderedCollection new: 400.
self cases: (OrderedCollection new: 400).
lastSel := selectorList first.
firstInRun := 0.
1 to: selectorList size do: [ :i |
sel := selectorList at: i.
sel ~= lastSel ifTrue: [
firsts add: firstInRun.
lasts add: i - 2.
stmt := TSendNode new setSelector: lastSel receiver: selfNode arguments: anArray.
case := cases add: (TStatementListNode new setArguments: #() statements: (Array with: stmt)).
case parent: self.
lastSel := sel.
firstInRun := i - 1.
].
].
firsts add: firstInRun.
lasts add: selectorList size - 1.
stmt := TSendNode new setSelector: lastSel receiver: selfNode arguments: anArray.
case := cases add: (TStatementListNode new setArguments: #() statements: (Array with: stmt)).
case parent: self.
]