1// Copyright (c) 2013, 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#include "vm/compiler/jit/jit_call_specializer.h"
5
6#include "vm/bit_vector.h"
7#include "vm/compiler/backend/branch_optimizer.h"
8#include "vm/compiler/backend/flow_graph_compiler.h"
9#include "vm/compiler/backend/il.h"
10#include "vm/compiler/backend/il_printer.h"
11#include "vm/compiler/backend/inliner.h"
12#include "vm/compiler/backend/range_analysis.h"
13#include "vm/compiler/cha.h"
14#include "vm/compiler/frontend/flow_graph_builder.h"
15#include "vm/compiler/jit/compiler.h"
16#include "vm/cpu.h"
17#include "vm/dart_entry.h"
18#include "vm/exceptions.h"
19#include "vm/hash_map.h"
20#include "vm/object_store.h"
21#include "vm/parser.h"
22#include "vm/resolver.h"
23#include "vm/scopes.h"
24#include "vm/stack_frame.h"
25#include "vm/symbols.h"
26
27namespace dart {
28
29// Quick access to the current isolate and zone.
30#define I (isolate())
31#define Z (zone())
32
33JitCallSpecializer::JitCallSpecializer(
34 FlowGraph* flow_graph,
35 SpeculativeInliningPolicy* speculative_policy)
36 : CallSpecializer(flow_graph,
37 speculative_policy,
38 Field::ShouldCloneFields()) {}
39
40bool JitCallSpecializer::IsAllowedForInlining(intptr_t deopt_id) const {
41 return true;
42}
43
44bool JitCallSpecializer::TryOptimizeStaticCallUsingStaticTypes(
45 StaticCallInstr* call) {
46 return false;
47}
48
49void JitCallSpecializer::ReplaceWithStaticCall(InstanceCallInstr* instr,
50 const Function& target,
51 intptr_t call_count) {
52 StaticCallInstr* call =
53 StaticCallInstr::FromCall(Z, instr, target, call_count);
54 const CallTargets& targets = instr->Targets();
55 if (targets.IsMonomorphic() && targets.MonomorphicExactness().IsExact()) {
56 if (targets.MonomorphicExactness().IsTriviallyExact()) {
57 flow_graph()->AddExactnessGuard(instr, targets.MonomorphicReceiverCid());
58 }
59 call->set_entry_kind(Code::EntryKind::kUnchecked);
60 }
61 instr->ReplaceWith(call, current_iterator());
62}
63
64// Tries to optimize instance call by replacing it with a faster instruction
65// (e.g, binary op, field load, ..).
66// TODO(dartbug.com/30635) Evaluate how much this can be shared with
67// AotCallSpecializer.
68void JitCallSpecializer::VisitInstanceCall(InstanceCallInstr* instr) {
69 const CallTargets& targets = instr->Targets();
70 if (targets.is_empty()) {
71 return; // No feedback.
72 }
73
74 const Token::Kind op_kind = instr->token_kind();
75
76 // Type test is special as it always gets converted into inlined code.
77 if (Token::IsTypeTestOperator(op_kind)) {
78 ReplaceWithInstanceOf(instr);
79 return;
80 }
81
82 if ((op_kind == Token::kASSIGN_INDEX) && TryReplaceWithIndexedOp(instr)) {
83 return;
84 }
85 if ((op_kind == Token::kINDEX) && TryReplaceWithIndexedOp(instr)) {
86 return;
87 }
88
89 if (op_kind == Token::kEQ && TryReplaceWithEqualityOp(instr, op_kind)) {
90 return;
91 }
92
93 if (Token::IsRelationalOperator(op_kind) &&
94 TryReplaceWithRelationalOp(instr, op_kind)) {
95 return;
96 }
97
98 if (Token::IsBinaryOperator(op_kind) &&
99 TryReplaceWithBinaryOp(instr, op_kind)) {
100 return;
101 }
102 if (Token::IsUnaryOperator(op_kind) &&
103 TryReplaceWithUnaryOp(instr, op_kind)) {
104 return;
105 }
106 if ((op_kind == Token::kGET) && TryInlineInstanceGetter(instr)) {
107 return;
108 }
109 if ((op_kind == Token::kSET) && TryInlineInstanceSetter(instr)) {
110 return;
111 }
112 if (TryInlineInstanceMethod(instr)) {
113 return;
114 }
115
116 bool has_one_target = targets.HasSingleTarget();
117 if (has_one_target) {
118 // Check if the single target is a polymorphic target, if it is,
119 // we don't have one target.
120 const Function& target = targets.FirstTarget();
121 if (target.recognized_kind() == MethodRecognizer::kObjectRuntimeType) {
122 has_one_target = PolymorphicInstanceCallInstr::ComputeRuntimeType(
123 targets) != Type::null();
124 } else {
125 has_one_target = !target.is_polymorphic_target();
126 }
127 }
128
129 if (has_one_target) {
130 const Function& target = targets.FirstTarget();
131 if (flow_graph()->CheckForInstanceCall(instr, target.kind()) ==
132 FlowGraph::ToCheck::kNoCheck) {
133 ReplaceWithStaticCall(instr, target, targets.AggregateCallCount());
134 return;
135 }
136 }
137
138 // If there is only one target we can make this into a deopting class check,
139 // followed by a call instruction that does not check the class of the
140 // receiver. This enables a lot of optimizations because after the class
141 // check we can probably inline the call and not worry about side effects.
142 // However, this can fall down if new receiver classes arrive at this call
143 // site after we generated optimized code. This causes a deopt, and after a
144 // few deopts we won't optimize this function any more at all. Therefore for
145 // very polymorphic sites we don't make this optimization, keeping it as a
146 // regular checked PolymorphicInstanceCall, which falls back to the slow but
147 // non-deopting megamorphic call stub when it sees new receiver classes.
148 if (has_one_target && FLAG_polymorphic_with_deopt &&
149 (!instr->ic_data()->HasDeoptReason(ICData::kDeoptCheckClass) ||
150 targets.length() <= FLAG_max_polymorphic_checks)) {
151 // Type propagation has not run yet, we cannot eliminate the check.
152 AddReceiverCheck(instr);
153
154 // Call can still deoptimize, do not detach environment from instr.
155 const Function& target = targets.FirstTarget();
156 ReplaceWithStaticCall(instr, target, targets.AggregateCallCount());
157 } else {
158 PolymorphicInstanceCallInstr* call =
159 PolymorphicInstanceCallInstr::FromCall(Z, instr, targets,
160 /* complete = */ false);
161 instr->ReplaceWith(call, current_iterator());
162 }
163}
164
165void JitCallSpecializer::VisitStoreInstanceField(
166 StoreInstanceFieldInstr* instr) {
167 if (instr->IsUnboxedStore()) {
168 // Determine if this field should be unboxed based on the usage of getter
169 // and setter functions: The heuristic requires that the setter has a
170 // usage count of at least 1/kGetterSetterRatio of the getter usage count.
171 // This is to avoid unboxing fields where the setter is never or rarely
172 // executed.
173 const Field& field = instr->slot().field();
174 const String& field_name = String::Handle(Z, field.name());
175 const Class& owner = Class::Handle(Z, field.Owner());
176 const Function& getter =
177 Function::Handle(Z, owner.LookupGetterFunction(field_name));
178 const Function& setter =
179 Function::Handle(Z, owner.LookupSetterFunction(field_name));
180 bool unboxed_field = false;
181 if (!getter.IsNull() && !setter.IsNull()) {
182 if (field.is_double_initialized()) {
183 unboxed_field = true;
184 } else if ((setter.usage_counter() > 0) &&
185 ((FLAG_getter_setter_ratio * setter.usage_counter()) >=
186 getter.usage_counter())) {
187 unboxed_field = true;
188 }
189 }
190 if (!unboxed_field) {
191 if (Compiler::IsBackgroundCompilation()) {
192 isolate()->AddDeoptimizingBoxedField(field);
193 Compiler::AbortBackgroundCompilation(
194 DeoptId::kNone, "Unboxing instance field while compiling");
195 UNREACHABLE();
196 }
197 if (FLAG_trace_optimization || FLAG_trace_field_guards) {
198 THR_Print("Disabling unboxing of %s\n", field.ToCString());
199 if (!setter.IsNull()) {
200 THR_Print(" setter usage count: %" Pd "\n", setter.usage_counter());
201 }
202 if (!getter.IsNull()) {
203 THR_Print(" getter usage count: %" Pd "\n", getter.usage_counter());
204 }
205 }
206 ASSERT(field.IsOriginal());
207 field.set_is_unboxing_candidate(false);
208 field.DeoptimizeDependentCode();
209 } else {
210 flow_graph()->parsed_function().AddToGuardedFields(&field);
211 }
212 }
213}
214
215// Replace generic context allocation or cloning with a sequence of inlined
216// allocation and explicit initializing stores.
217// If context_value is not NULL then newly allocated context is a populated
218// with values copied from it, otherwise it is initialized with null.
219void JitCallSpecializer::LowerContextAllocation(
220 Definition* alloc,
221 const ZoneGrowableArray<const Slot*>& context_variables,
222 Value* context_value) {
223 ASSERT(alloc->IsAllocateContext() || alloc->IsCloneContext());
224
225 AllocateUninitializedContextInstr* replacement =
226 new AllocateUninitializedContextInstr(alloc->token_pos(),
227 context_variables.length());
228 alloc->ReplaceWith(replacement, current_iterator());
229
230 Instruction* cursor = replacement;
231
232 Value* initial_value;
233 if (context_value != NULL) {
234 LoadFieldInstr* load =
235 new (Z) LoadFieldInstr(context_value->CopyWithType(Z),
236 Slot::Context_parent(), alloc->token_pos());
237 flow_graph()->InsertAfter(cursor, load, NULL, FlowGraph::kValue);
238 cursor = load;
239 initial_value = new (Z) Value(load);
240 } else {
241 initial_value = new (Z) Value(flow_graph()->constant_null());
242 }
243 StoreInstanceFieldInstr* store = new (Z) StoreInstanceFieldInstr(
244 Slot::Context_parent(), new (Z) Value(replacement), initial_value,
245 kNoStoreBarrier, alloc->token_pos(),
246 StoreInstanceFieldInstr::Kind::kInitializing);
247 flow_graph()->InsertAfter(cursor, store, nullptr, FlowGraph::kEffect);
248 cursor = replacement;
249
250 for (auto& slot : context_variables) {
251 if (context_value != nullptr) {
252 LoadFieldInstr* load = new (Z) LoadFieldInstr(
253 context_value->CopyWithType(Z), *slot, alloc->token_pos());
254 flow_graph()->InsertAfter(cursor, load, nullptr, FlowGraph::kValue);
255 cursor = load;
256 initial_value = new (Z) Value(load);
257 } else {
258 initial_value = new (Z) Value(flow_graph()->constant_null());
259 }
260
261 store = new (Z) StoreInstanceFieldInstr(
262 *slot, new (Z) Value(replacement), initial_value, kNoStoreBarrier,
263 alloc->token_pos(), StoreInstanceFieldInstr::Kind::kInitializing);
264 flow_graph()->InsertAfter(cursor, store, nullptr, FlowGraph::kEffect);
265 cursor = store;
266 }
267}
268
269void JitCallSpecializer::VisitAllocateContext(AllocateContextInstr* instr) {
270 LowerContextAllocation(instr, instr->context_slots(), nullptr);
271}
272
273void JitCallSpecializer::VisitCloneContext(CloneContextInstr* instr) {
274 LowerContextAllocation(instr, instr->context_slots(), instr->context_value());
275}
276
277} // namespace dart
278