1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #ifndef RUNTIME_VM_COMPILER_CALL_SPECIALIZER_H_ |
6 | #define RUNTIME_VM_COMPILER_CALL_SPECIALIZER_H_ |
7 | |
8 | #if defined(DART_PRECOMPILED_RUNTIME) |
9 | #error "AOT runtime should not use compiler sources (including header files)" |
10 | #endif // defined(DART_PRECOMPILED_RUNTIME) |
11 | |
12 | #include "vm/compiler/backend/flow_graph.h" |
13 | #include "vm/compiler/backend/il.h" |
14 | |
15 | namespace dart { |
16 | |
17 | class SpeculativeInliningPolicy; |
18 | |
19 | // Call specialization pass is responsible for replacing instance calls by |
20 | // faster alternatives based on type feedback (JIT), type speculations (AOT), |
21 | // locally propagated type information or global type information. |
22 | // |
23 | // This pass for example can |
24 | // |
25 | // * Replace a call to a binary arithmetic operator with corresponding IL |
26 | // instructions and necessary checks; |
27 | // * Replace a dynamic call with a static call, if reciever is known |
28 | // to have a certain class id; |
29 | // * Replace type check with a range check |
30 | // |
31 | // CallSpecializer is a base class that contains logic shared between |
32 | // JIT and AOT compilation pipelines, see JitCallSpecializer for JIT specific |
33 | // optimizations and AotCallSpecializer for AOT specific optimizations. |
34 | class CallSpecializer : public FlowGraphVisitor { |
35 | public: |
36 | CallSpecializer(FlowGraph* flow_graph, |
37 | SpeculativeInliningPolicy* speculative_policy, |
38 | bool should_clone_fields) |
39 | : FlowGraphVisitor(flow_graph->reverse_postorder()), |
40 | speculative_policy_(speculative_policy), |
41 | should_clone_fields_(should_clone_fields), |
42 | flow_graph_(flow_graph) {} |
43 | |
44 | virtual ~CallSpecializer() {} |
45 | |
46 | FlowGraph* flow_graph() const { return flow_graph_; } |
47 | |
48 | void set_flow_graph(FlowGraph* flow_graph) { |
49 | flow_graph_ = flow_graph; |
50 | set_block_order(flow_graph->reverse_postorder()); |
51 | } |
52 | |
53 | // Use ICData to optimize, replace or eliminate instructions. |
54 | void ApplyICData(); |
55 | |
56 | // Use propagated class ids to optimize, replace or eliminate instructions. |
57 | void ApplyClassIds(); |
58 | |
59 | virtual void ReplaceInstanceCallsWithDispatchTableCalls(); |
60 | |
61 | void InsertBefore(Instruction* next, |
62 | Instruction* instr, |
63 | Environment* env, |
64 | FlowGraph::UseKind use_kind) { |
65 | flow_graph_->InsertBefore(next, instr, env, use_kind); |
66 | } |
67 | |
68 | virtual void VisitStaticCall(StaticCallInstr* instr); |
69 | |
70 | // TODO(dartbug.com/30633) these methods have nothing to do with |
71 | // specialization of calls. They are here for historical reasons. |
72 | // Find a better place for them. |
73 | virtual void VisitLoadCodeUnits(LoadCodeUnitsInstr* instr); |
74 | |
75 | protected: |
76 | Thread* thread() const { return flow_graph_->thread(); } |
77 | Isolate* isolate() const { return flow_graph_->isolate(); } |
78 | Zone* zone() const { return flow_graph_->zone(); } |
79 | const Function& function() const { return flow_graph_->function(); } |
80 | |
81 | bool TryReplaceWithIndexedOp(InstanceCallInstr* call); |
82 | |
83 | bool TryReplaceWithBinaryOp(InstanceCallInstr* call, Token::Kind op_kind); |
84 | bool TryReplaceWithUnaryOp(InstanceCallInstr* call, Token::Kind op_kind); |
85 | |
86 | bool TryReplaceWithEqualityOp(InstanceCallInstr* call, Token::Kind op_kind); |
87 | bool TryReplaceWithRelationalOp(InstanceCallInstr* call, Token::Kind op_kind); |
88 | |
89 | bool TryInlineInstanceGetter(InstanceCallInstr* call); |
90 | bool TryInlineInstanceSetter(InstanceCallInstr* call); |
91 | |
92 | bool TryInlineInstanceMethod(InstanceCallInstr* call); |
93 | void ReplaceWithInstanceOf(InstanceCallInstr* instr); |
94 | |
95 | // Replaces a call where the replacement code does not end in a |
96 | // value-returning instruction, so we must specify what definition should be |
97 | // used instead to replace uses of the call return value. |
98 | void ReplaceCallWithResult(Definition* call, |
99 | Instruction* replacement, |
100 | Definition* result); |
101 | void ReplaceCall(Definition* call, Definition* replacement); |
102 | |
103 | // Add a class check for the call's first argument (receiver). |
104 | void AddReceiverCheck(InstanceCallInstr* call) { |
105 | AddCheckClass(call->Receiver()->definition(), call->Targets(), |
106 | call->deopt_id(), call->env(), call); |
107 | } |
108 | |
109 | // Insert a null check if needed. |
110 | void AddCheckNull(Value* to_check, |
111 | const String& function_name, |
112 | intptr_t deopt_id, |
113 | Environment* deopt_environment, |
114 | Instruction* insert_before); |
115 | |
116 | // Attempt to build ICData for call using propagated class-ids. |
117 | virtual bool TryCreateICData(InstanceCallInstr* call); |
118 | |
119 | virtual bool TryReplaceInstanceOfWithRangeCheck(InstanceCallInstr* call, |
120 | const AbstractType& type); |
121 | |
122 | virtual bool TryOptimizeStaticCallUsingStaticTypes(StaticCallInstr* call) = 0; |
123 | |
124 | protected: |
125 | void InlineImplicitInstanceGetter(Definition* call, const Field& field); |
126 | |
127 | // Insert a check of 'to_check' determined by 'unary_checks'. If the |
128 | // check fails it will deoptimize to 'deopt_id' using the deoptimization |
129 | // environment 'deopt_environment'. The check is inserted immediately |
130 | // before 'insert_before'. |
131 | void AddCheckClass(Definition* to_check, |
132 | const Cids& cids, |
133 | intptr_t deopt_id, |
134 | Environment* deopt_environment, |
135 | Instruction* insert_before); |
136 | |
137 | SpeculativeInliningPolicy* speculative_policy_; |
138 | const bool should_clone_fields_; |
139 | |
140 | private: |
141 | bool TypeCheckAsClassEquality(const AbstractType& type); |
142 | |
143 | // Insert a Smi check if needed. |
144 | void AddCheckSmi(Definition* to_check, |
145 | intptr_t deopt_id, |
146 | Environment* deopt_environment, |
147 | Instruction* insert_before); |
148 | |
149 | // Add a class check for a call's nth argument immediately before the |
150 | // call, using the call's IC data to determine the check, and the call's |
151 | // deopt ID and deoptimization environment if the check fails. |
152 | void AddChecksForArgNr(InstanceCallInstr* call, |
153 | Definition* argument, |
154 | int argument_number); |
155 | |
156 | bool InlineSimdBinaryOp(InstanceCallInstr* call, |
157 | intptr_t cid, |
158 | Token::Kind op_kind); |
159 | |
160 | bool TryInlineImplicitInstanceGetter(InstanceCallInstr* call); |
161 | |
162 | BoolPtr InstanceOfAsBool(const ICData& ic_data, |
163 | const AbstractType& type, |
164 | ZoneGrowableArray<intptr_t>* results) const; |
165 | |
166 | bool TryOptimizeInstanceOfUsingStaticTypes(InstanceCallInstr* call, |
167 | const AbstractType& type); |
168 | |
169 | void ReplaceWithMathCFunction(InstanceCallInstr* call, |
170 | MethodRecognizer::Kind recognized_kind); |
171 | |
172 | bool TryStringLengthOneEquality(InstanceCallInstr* call, Token::Kind op_kind); |
173 | |
174 | void SpecializePolymorphicInstanceCall(PolymorphicInstanceCallInstr* call); |
175 | |
176 | // Tries to add cid tests to 'results' so that no deoptimization is |
177 | // necessary for common number-related type tests. Unconditionally adds an |
178 | // entry for the Smi type to the start of the array. |
179 | static bool SpecializeTestCidsForNumericTypes( |
180 | ZoneGrowableArray<intptr_t>* results, |
181 | const AbstractType& type); |
182 | |
183 | FlowGraph* flow_graph_; |
184 | }; |
185 | |
186 | #define PUBLIC_TYPED_DATA_CLASS_LIST(V) \ |
187 | V(Int8List, int8_list_type_, int_type_, kTypedDataInt8ArrayCid) \ |
188 | V(Uint8List, uint8_list_type_, int_type_, kTypedDataUint8ArrayCid) \ |
189 | V(Uint8ClampedList, uint8_clamped_type_, int_type_, \ |
190 | kTypedDataUint8ClampedArrayCid) \ |
191 | V(Int16List, int16_list_type_, int_type_, kTypedDataInt16ArrayCid) \ |
192 | V(Uint16List, uint16_list_type_, int_type_, kTypedDataUint16ArrayCid) \ |
193 | V(Int32List, int32_list_type_, int_type_, kTypedDataInt32ArrayCid) \ |
194 | V(Uint32List, uint32_list_type_, int_type_, kTypedDataUint32ArrayCid) \ |
195 | V(Int64List, int64_list_type_, int_type_, kTypedDataInt64ArrayCid) \ |
196 | V(Uint64List, uint64_list_type_, int_type_, kTypedDataUint64ArrayCid) \ |
197 | V(Float32List, float32_list_type_, double_type_, kTypedDataFloat32ArrayCid) \ |
198 | V(Float64List, float64_list_type_, double_type_, kTypedDataFloat64ArrayCid) |
199 | |
200 | // Specializes instance/static calls with receiver type being a typed data |
201 | // interface (if that interface is only implemented by internal/external/view |
202 | // typed data classes). |
203 | // |
204 | // For example: |
205 | // |
206 | // foo(Uint8List bytes) => bytes[0]; |
207 | // |
208 | // Would be translated to something like this: |
209 | // |
210 | // v0 <- Constant(0) |
211 | // |
212 | // // Ensures the list is non-null. |
213 | // v1 <- ParameterInstr(0) |
214 | // v2 <- CheckNull(v1) |
215 | // |
216 | // // Load the length & perform bounds checks |
217 | // v3 <- LoadField(v2, "TypedDataBase.length"); |
218 | // v4 <- GenericCheckBounds(v3, v0); |
219 | // |
220 | // // Directly access the byte, independent of whether `bytes` is |
221 | // // _Uint8List, _Uint8ArrayView or _ExternalUint8Array. |
222 | // v5 <- LoadUntagged(v1, "TypedDataBase.data"); |
223 | // v5 <- LoadIndexed(v5, v4) |
224 | // |
225 | class TypedDataSpecializer : public FlowGraphVisitor { |
226 | public: |
227 | static void Optimize(FlowGraph* flow_graph); |
228 | |
229 | virtual void VisitInstanceCall(InstanceCallInstr* instr); |
230 | virtual void VisitStaticCall(StaticCallInstr* instr); |
231 | |
232 | private: |
233 | // clang-format off |
234 | explicit TypedDataSpecializer(FlowGraph* flow_graph) |
235 | : FlowGraphVisitor(flow_graph->reverse_postorder()), |
236 | thread_(Thread::Current()), |
237 | zone_(thread_->zone()), |
238 | flow_graph_(flow_graph), |
239 | #define ALLOCATE_HANDLE(iface, member_name, type, cid) \ |
240 | member_name(AbstractType::Handle(zone_)), |
241 | PUBLIC_TYPED_DATA_CLASS_LIST(ALLOCATE_HANDLE) |
242 | #undef INIT_HANDLE |
243 | int_type_(AbstractType::Handle()), |
244 | double_type_(AbstractType::Handle()), |
245 | implementor_(Class::Handle()) { |
246 | } |
247 | // clang-format on |
248 | |
249 | void EnsureIsInitialized(); |
250 | bool HasThirdPartyImplementor(const GrowableObjectArray& direct_implementors); |
251 | void TryInlineCall(TemplateDartCall<0>* call); |
252 | void ReplaceWithLengthGetter(TemplateDartCall<0>* call); |
253 | void ReplaceWithIndexGet(TemplateDartCall<0>* call, classid_t cid); |
254 | void ReplaceWithIndexSet(TemplateDartCall<0>* call, classid_t cid); |
255 | void AppendNullCheck(TemplateDartCall<0>* call, Definition** array); |
256 | void AppendBoundsCheck(TemplateDartCall<0>* call, |
257 | Definition* array, |
258 | Definition** index); |
259 | Definition* AppendLoadLength(TemplateDartCall<0>* call, Definition* array); |
260 | Definition* AppendLoadIndexed(TemplateDartCall<0>* call, |
261 | Definition* array, |
262 | Definition* index, |
263 | classid_t cid); |
264 | void AppendStoreIndexed(TemplateDartCall<0>* call, |
265 | Definition* array, |
266 | Definition* index, |
267 | Definition* value, |
268 | classid_t cid); |
269 | |
270 | Zone* zone() const { return zone_; } |
271 | |
272 | Thread* thread_; |
273 | Zone* zone_; |
274 | FlowGraph* flow_graph_; |
275 | bool initialized_ = false; |
276 | |
277 | #define DEF_HANDLE(iface, member_name, type, cid) AbstractType& member_name; |
278 | PUBLIC_TYPED_DATA_CLASS_LIST(DEF_HANDLE) |
279 | #undef DEF_HANDLE |
280 | |
281 | AbstractType& int_type_; |
282 | AbstractType& double_type_; |
283 | Class& implementor_; |
284 | }; |
285 | |
286 | } // namespace dart |
287 | |
288 | #endif // RUNTIME_VM_COMPILER_CALL_SPECIALIZER_H_ |
289 | |