/
DominatorFinder.class.st
337 lines (309 loc) · 12.9 KB
/
DominatorFinder.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
"
A DominatorFinder is an InstructionStream that finds the dominators of bytecodes. Specifically it aims to find the dominating conditional branches for join points. This is part of the register allocation problem, to know the common height of the stack at a join point. Only items above the common height need to be merged.
I believe finding dominators in bytecode can be done with a simple scan using an FSM, e.g. in scanMethod. This class is part of an experiment to find out if I'm right.
I observe that
- the first conditional branch that branches to a target that is preceded by an unconditional branch dominates the target of the unconditional branch
- if no conditional branch that branches to a target, branches to a target preceded by an unconditional branch (i.e. all are preceded by returns) then the first conditional branch dominates the target
- a conditional branch that branches to a target preceded by a backward branch dominates its target (loops)
This DominatorFinder attempts to find conditional branches that dominate join points, while filtering out conditional branches that dominate non-joins (i.e. those following single returning ifs and those that jump to the end of loops).
Instance Variables
cameFroms: <Array>
dominators: <Dictionary>
encoderClass: <BytecodeEncoder>
previousInstruction: <Symbol>
thisInstruction: <Symbol>
thisPC: <Integer>
cameFroms
- the pcs of dominating conditional branches
dominators
- dictionary of dominating pc to dominated pc
encoderClass
- the encoderClass for the current method
previousInstruction
- the selector of the Message for the previous bytecode during the scan
thisInstruction
- the selector of the Message for the current bytecode during the scan
thisPC
- the pc for the current bytecode during the scan
"
Class {
#name : #DominatorFinder,
#superclass : #InstructionStream,
#instVars : [
'cameFroms',
'dominators',
'encoderClass',
'thisInstruction',
'previousInstruction',
'jumpTarget',
'thisPC'
],
#classVars : [
'ReturnSelectors'
],
#category : #'Cog-Explorations'
}
{ #category : #exploration }
DominatorFinder class >> containsAssignedOptimizedConditionalExpressionValue: aCompiledMethod [
"Answer if aCompiledMethod contains an optimized conditional expression which is assigned."
[:dominatingNodes
:anomalousNodes
:dominatedNodes
:dominatorMap
:newMethod| | encoderClass |
^dominatorMap notEmpty
and: [(encoderClass := newMethod encoderClass) supportsFullBlocks
ifFalse: "simple; can look at the single method object"
[| sps |
sps := (StackDepthFinder on: newMethod) stackPointers.
dominatorMap associations anySatisfy:
[:a| "Filter out expr ifTrue: [...] ifFalse: [...]. Both arms share a single pop"
((encoderClass isStorePopAt: a value in: newMethod)
or: [encoderClass isStoreAt: a value in: newMethod])
and: [(sps at: a key) <= (sps at: a value)]]]
ifTrue: "complex; have to locate the relevant sub-method"
[| asps |
asps := (StackDepthFinder on: newMethod) allStackPointers.
dominatorMap associations anySatisfy:
[:a| | dpc sps m |
a key isInteger
ifTrue:
[dpc := a key.
m := newMethod]
ifFalse:
[dpc := a key value.
m := a key key].
sps := asps at: m.
"Filter out expr ifTrue: [...] ifFalse: [...]. Both arms share a single pop"
((encoderClass isStorePopAt: a value in: m)
or: [encoderClass isStoreAt: a value in: m])
and: [(sps at: dpc) <= (sps at: a value)]]]]]
valueWithArguments: (self dominatorTupleForMethod: aCompiledMethod)
]
{ #category : #exploration }
DominatorFinder class >> containsOptimizedConditionalExpressionValue: aCompiledMethod [
"Answer if aCompiledMethod contains an optimized conditional expression which
is used as either a message receiver or parameter, or a value to store or return."
[:dominatingNodes
:anomalousNodes
:dominatedNodes
:dominatorMap
:newMethod| | encoderClass |
^dominatorMap notEmpty
and: [(encoderClass := newMethod encoderClass) supportsFullBlocks
ifFalse: "simple; can look at the single method object"
[| sps |
sps := (StackDepthFinder on: newMethod) stackPointers.
dominatorMap associations anySatisfy:
[:a| "Filter out expr ifTrue: [...] ifFalse: [...]. Both arms share a single pop"
(encoderClass isJustPopAt: a value in: newMethod) not
and: [(sps at: a key) <= (sps at: a value)]]]
ifTrue: "complex; have to locate the relevant sub-method"
[| asps |
asps := (StackDepthFinder on: newMethod) allStackPointers.
dominatorMap associations anySatisfy:
[:a| | dpc sps m |
a key isInteger
ifTrue:
[dpc := a key.
m := newMethod]
ifFalse:
[dpc := a key value.
m := a key key].
sps := asps at: m.
"Filter out expr ifTrue: [...] ifFalse: [...]. Both arms share a single pop"
(encoderClass isJustPopAt: a value in: m) not
and: [(sps at: dpc) <= (sps at: a value)]]]]]
valueWithArguments: (self dominatorTupleForMethod: aCompiledMethod)
]
{ #category : #exploration }
DominatorFinder class >> containsOptimizedConditionalExpressionValueOtherThanForBranching: aCompiledMethod [
"Answer if aCompiledMethod contains an optimized conditional expression which
is used as either a message receiver or parameter, or a value to store or return,
but not as a receiver to be branched upon."
[:dominatingNodes
:anomalousNodes
:dominatedNodes
:dominatorMap
:newMethod| | encoderClass |
^dominatorMap notEmpty
and: [(encoderClass := newMethod encoderClass) supportsFullBlocks
ifFalse: "simple; can look at the single method object"
[| sps |
sps := (StackDepthFinder on: newMethod) stackPointers.
dominatorMap associations anySatisfy:
[:a| "Filter out expr ifTrue: [...] ifFalse: [...]. Both arms share a single pop"
(encoderClass isJustPopAt: a value in: newMethod) not
and: [(encoderClass isBranchAt: a value in: newMethod) not
and: [(sps at: a key) <= (sps at: a value)]]]]
ifTrue: "complex; have to locate the relevant sub-method"
[| asps |
asps := (StackDepthFinder on: newMethod) allStackPointers.
dominatorMap associations anySatisfy:
[:a| | dpc sps m |
a key isInteger
ifTrue:
[dpc := a key.
m := newMethod]
ifFalse:
[dpc := a key value.
m := a key key].
sps := asps at: m.
"Filter out expr ifTrue: [...] ifFalse: [...]. Both arms share a single pop"
(encoderClass isJustPopAt: a value in: m) not
and: [(encoderClass isBranchAt: a value in: m) not
and: [(sps at: dpc) <= (sps at: a value)]]]]]]
valueWithArguments: (self dominatorTupleForMethod: aCompiledMethod)
]
{ #category : #exploration }
DominatorFinder class >> dominatorTupleForMethod: aCompiledMethod [
"Answer a tuple of
dominating optimized nodes, (inlined if's that are found by DominatorFinder)
anomalous optimized nodes, (inlined if's that are not found by DominatorFinder)
dominated nodes, (inlined if's nested within other inlined if's that occur at the end of a basic block and hence jump to the same place as their parent)
dominator pcs (the dominator pcs found by the DominatorFinder)
recompiled method
for aCompiledMethod. It is expected that the anomaloius nodes should be empty."
| mn optimizedNodes
dominators offenders dominated dominatorPCs |
aCompiledMethod isQuick ifTrue:
[^#(#() #() #() #() #())].
mn := CompiledMethod
noCheckSetPreferredBytecodeSetTo: aCompiledMethod encoderClass
while:
[[:c :s|
[c compile:(c sourceCodeAt: s)
environment: c environment
notifying: nil
trailer: aCompiledMethod trailer
ifFail: [nil]]
on: SyntaxErrorNotification
do: [^#(#() #() #() #() #())]]
value: aCompiledMethod methodClass
value: aCompiledMethod selector].
"mn method ~= aCompiledMethod ifTrue:
[Transcript cr; show: 'warning: recompilation of ', aCompiledMethod reference, ' generated different code'.
^#(#() #() #() #() #())]."
dominatorPCs := (self on: mn method) dominators.
dominated := IdentitySet new. "The last statement of an inlined if cannot dominate the join of its enclosing if"
optimizedNodes := OrderedCollection new. "Avoid double traversal"
mn node nodesDo:
[:n| | lastStatement |
(n isMessage and: [n isOptimized]) ifTrue:
[optimizedNodes addLast: n.
n isOptimizedConditional ifTrue:
[lastStatement := n lastBlockOfOptimizedConditional statements last.
(lastStatement isMessage and: [lastStatement isOptimizedConditional]) ifTrue:
[dominated add: lastStatement]]]].
dominators := OrderedCollection new: optimizedNodes size.
offenders := OrderedCollection new: optimizedNodes size.
optimizedNodes do:
[:n|
(n isOptimizedLoop not "Loop CBs do dominate, but their target is not a join point"
and: [n isSingleReturningIf not "ifTrue: [^foo] CBs do dominate but their target is not a join point"
and: [n isEmptyIf not "ifTrue: [] generates no code"
and: [n pc ~= 0 "caseOf: nodes get assigned a pc of 0; go figure..."
and: [(dominated includes: n) not]]]]) ifTrue:
[((dominatorPCs at: n pc ifAbsent: nil)
ifNil: [offenders]
ifNotNil: [dominators]) addLast: n]].
^{ dominators. offenders. dominated. dominatorPCs. mn method }
]
{ #category : #exploration }
DominatorFinder class >> exampleMethod [
"(StackDepthFinder on: DominatorFinder class >> #exampleMethod) stackPointers.
DominatorFinder dominatorTupleForMethod: DominatorFinder class >> #exampleMethod.
(DominatorFinder class >> #exampleMethod) detailedSymbolic"
self isCollection
ifTrue: [self at: 1]
ifFalse: [self at: 2].
self at: (self isCollection ifTrue: [1] ifFalse: [2]) put: self
]
{ #category : #'class initialization' }
DominatorFinder class >> initialize [
"self initialize"
ReturnSelectors := ((self systemNavigation allCallsOn: #return:from: localTo: Context) collect: [:mr| mr selector]) as: IdentitySet.
]
{ #category : #'message handling' }
DominatorFinder >> doesNotUnderstand: aMessage [
self recordThisInstruction: aMessage selector
]
{ #category : #accessing }
DominatorFinder >> dominators [
"Scan to find the dominating conditional branches."
| end |
end := self method endPC.
[pc <= end] whileTrue:
[self interpretNextInstructionFor: self].
^dominators
]
{ #category : #decoding }
DominatorFinder >> interpretNextInstructionFor: client [
| result |
(cameFroms at: pc) ifNotNil:
[:cameFromPC|
"the first conditional branch that branches to a target that is preceded by an unconditional forward branch dominates the target of the unconditional branch.
(an unconditional backward branch indicates a loop)"
(previousInstruction == #jump:
and: [jumpTarget >= pc])
ifTrue:
[(cameFroms at: jumpTarget) ifNil:
[self assert: (dominators includesKey: cameFromPC) not.
dominators at: cameFromPC put: jumpTarget]]
ifFalse:
"the first conditional branch that branches to a target not preceded by an unconditional branch dominates the target of the conditional branch"
[(dominators at: cameFromPC ifAbsent: nil) ifNil:
[dominators at: cameFromPC put: pc]]].
thisPC := pc.
result := encoderClass interpretNextInstructionFor: client in: self.
previousInstruction := thisInstruction
]
{ #category : #private }
DominatorFinder >> isReturn: aMessageSelector [
^ReturnSelectors includes: aMessageSelector
]
{ #category : #'instruction decoding' }
DominatorFinder >> jump: distance [
jumpTarget := pc + distance.
self recordThisInstruction: #jump:
]
{ #category : #'instruction decoding' }
DominatorFinder >> jump: distance if: condition [
| target |
target := pc + distance.
(cameFroms at: target)
ifNil: [cameFroms at: target put: thisPC]
ifNotNil: [:cameFromPC| self assert: cameFromPC < thisPC].
self recordThisInstruction: #jump:if:
]
{ #category : #private }
DominatorFinder >> method: method pc: startpc [
super method: method pc: startpc.
cameFroms := Array new: method endPC.
encoderClass := method encoderClass.
dominators := Dictionary new
]
{ #category : #'instruction decoding' }
DominatorFinder >> pushFullClosure: aCompiledBlock numCopied: numCopied [
self recordThisInstruction: #pushFullClosure:numCopied:;
recurseIntoBlock: aCompiledBlock
]
{ #category : #'instruction decoding' }
DominatorFinder >> pushFullClosure: aCompiledBlock numCopied: numCopied receiverOnStack: rcvrOnStack ignoreOuterContext: ignoreOuterContext [
self recordThisInstruction: #pushFullClosure:numCopied:receiverOnStack:ignoreOuterContext:;
recurseIntoBlock: aCompiledBlock
]
{ #category : #private }
DominatorFinder >> recordThisInstruction: aSelector [
thisInstruction := aSelector
]
{ #category : #private }
DominatorFinder >> recurseIntoBlock: aCompiledBlock [
"Recurse into a full block. Map its dominating pcs appropriately to match
BytecodeEncoder>>nextPC's representyation for the pcs in nested full blocks."
(self class on: aCompiledBlock) dominators keysAndValuesDo:
[:key :value|
dominators
at: (key isInteger ifTrue: [aCompiledBlock -> key] ifFalse: [key])
put: value]
]