1 // Copyright 2019 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/compiler/csa-load-elimination.h"
6
7 #include "src/compiler/common-operator.h"
8 #include "src/compiler/node-matchers.h"
9 #include "src/compiler/node-properties.h"
10 #include "src/compiler/simplified-operator.h"
11
12 namespace v8 {
13 namespace internal {
14 namespace compiler {
15
Reduce(Node * node)16 Reduction CsaLoadElimination::Reduce(Node* node) {
17 if (FLAG_trace_turbo_load_elimination) {
18 if (node->op()->EffectInputCount() > 0) {
19 PrintF(" visit #%d:%s", node->id(), node->op()->mnemonic());
20 if (node->op()->ValueInputCount() > 0) {
21 PrintF("(");
22 for (int i = 0; i < node->op()->ValueInputCount(); ++i) {
23 if (i > 0) PrintF(", ");
24 Node* const value = NodeProperties::GetValueInput(node, i);
25 PrintF("#%d:%s", value->id(), value->op()->mnemonic());
26 }
27 PrintF(")");
28 }
29 PrintF("\n");
30 for (int i = 0; i < node->op()->EffectInputCount(); ++i) {
31 Node* const effect = NodeProperties::GetEffectInput(node, i);
32 if (AbstractState const* const state = node_states_.Get(effect)) {
33 PrintF(" state[%i]: #%d:%s\n", i, effect->id(),
34 effect->op()->mnemonic());
35 state->Print();
36 } else {
37 PrintF(" no state[%i]: #%d:%s\n", i, effect->id(),
38 effect->op()->mnemonic());
39 }
40 }
41 }
42 }
43 switch (node->opcode()) {
44 case IrOpcode::kLoadFromObject:
45 return ReduceLoadFromObject(node, ObjectAccessOf(node->op()));
46 case IrOpcode::kStoreToObject:
47 return ReduceStoreToObject(node, ObjectAccessOf(node->op()));
48 case IrOpcode::kDebugBreak:
49 case IrOpcode::kAbortCSADcheck:
50 // Avoid changing optimizations in the presence of debug instructions.
51 return PropagateInputState(node);
52 case IrOpcode::kCall:
53 return ReduceCall(node);
54 case IrOpcode::kEffectPhi:
55 return ReduceEffectPhi(node);
56 case IrOpcode::kDead:
57 return NoChange();
58 case IrOpcode::kStart:
59 return ReduceStart(node);
60 default:
61 return ReduceOtherNode(node);
62 }
63 UNREACHABLE();
64 }
65
66 namespace CsaLoadEliminationHelpers {
67
Subsumes(MachineRepresentation from,MachineRepresentation to)68 bool Subsumes(MachineRepresentation from, MachineRepresentation to) {
69 if (from == to) return true;
70 if (IsAnyTagged(from)) return IsAnyTagged(to);
71 if (IsIntegral(from)) {
72 return IsIntegral(to) && ElementSizeInBytes(from) >= ElementSizeInBytes(to);
73 }
74 return false;
75 }
76
IsConstantObject(Node * object)77 bool IsConstantObject(Node* object) {
78 return object->opcode() == IrOpcode::kParameter ||
79 object->opcode() == IrOpcode::kLoadImmutable ||
80 NodeProperties::IsConstant(object);
81 }
82
IsFreshObject(Node * object)83 bool IsFreshObject(Node* object) {
84 DCHECK_IMPLIES(NodeProperties::IsFreshObject(object),
85 !IsConstantObject(object));
86 return NodeProperties::IsFreshObject(object);
87 }
88
89 } // namespace CsaLoadEliminationHelpers
90
91 namespace Helpers = CsaLoadEliminationHelpers;
92
93 // static
94 template <typename OuterKey>
IntersectWith(OuterMap<OuterKey> & to,const OuterMap<OuterKey> & from)95 void CsaLoadElimination::AbstractState::IntersectWith(
96 OuterMap<OuterKey>& to, const OuterMap<OuterKey>& from) {
97 FieldInfo empty_info;
98 for (const std::pair<OuterKey, InnerMap>& to_map : to) {
99 InnerMap to_map_copy(to_map.second);
100 OuterKey key = to_map.first;
101 InnerMap current_map = from.Get(key);
102 for (std::pair<Node*, FieldInfo> info : to_map.second) {
103 if (current_map.Get(info.first) != info.second) {
104 to_map_copy.Set(info.first, empty_info);
105 }
106 }
107 to.Set(key, to_map_copy);
108 }
109 }
110
IntersectWith(AbstractState const * that)111 void CsaLoadElimination::AbstractState::IntersectWith(
112 AbstractState const* that) {
113 IntersectWith(fresh_entries_, that->fresh_entries_);
114 IntersectWith(constant_entries_, that->constant_entries_);
115 IntersectWith(arbitrary_entries_, that->arbitrary_entries_);
116 IntersectWith(fresh_unknown_entries_, that->fresh_unknown_entries_);
117 IntersectWith(constant_unknown_entries_, that->constant_unknown_entries_);
118 IntersectWith(arbitrary_unknown_entries_, that->arbitrary_unknown_entries_);
119 }
120
121 CsaLoadElimination::AbstractState const*
KillField(Node * object,Node * offset,MachineRepresentation repr) const122 CsaLoadElimination::AbstractState::KillField(Node* object, Node* offset,
123 MachineRepresentation repr) const {
124 AbstractState* result = zone_->New<AbstractState>(*this);
125 UnknownOffsetInfos empty_unknown(zone_, InnerMap(zone_));
126 IntPtrMatcher m(offset);
127 if (m.HasResolvedValue()) {
128 uint32_t num_offset = static_cast<uint32_t>(m.ResolvedValue());
129 if (Helpers::IsFreshObject(object)) {
130 // May alias with:
131 // - The same object/offset
132 // - Arbitrary objects with the same offset
133 // - The same object, unkwown offset
134 // - Arbitrary objects with unkwown offset
135 result->KillOffsetInFresh(object, num_offset, repr);
136 KillOffset(result->arbitrary_entries_, num_offset, repr, zone_);
137 result->fresh_unknown_entries_.Set(object, InnerMap(zone_));
138 result->arbitrary_unknown_entries_ = empty_unknown;
139 } else if (Helpers::IsConstantObject(object)) {
140 // May alias with:
141 // - Constant/arbitrary objects with the same offset
142 // - Constant/arbitrary objects with unkwown offset
143 KillOffset(result->constant_entries_, num_offset, repr, zone_);
144 KillOffset(result->arbitrary_entries_, num_offset, repr, zone_);
145 result->constant_unknown_entries_ = empty_unknown;
146 result->arbitrary_unknown_entries_ = empty_unknown;
147 } else {
148 // May alias with:
149 // - Any object with the same or unknown offset
150 KillOffset(result->fresh_entries_, num_offset, repr, zone_);
151 KillOffset(result->constant_entries_, num_offset, repr, zone_);
152 KillOffset(result->arbitrary_entries_, num_offset, repr, zone_);
153 result->fresh_unknown_entries_ = empty_unknown;
154 result->constant_unknown_entries_ = empty_unknown;
155 result->arbitrary_unknown_entries_ = empty_unknown;
156 }
157 } else {
158 ConstantOffsetInfos empty_constant(zone_, InnerMap(zone_));
159 if (Helpers::IsFreshObject(object)) {
160 // May alias with:
161 // - The same object with any known/unknown offset
162 // - Arbitrary objects with any known/unknown offset
163 for (auto map : result->fresh_entries_) {
164 // TODO(manoskouk): Consider adding a map from fresh objects to offsets
165 // to implement this efficiently.
166 InnerMap map_copy(map.second);
167 map_copy.Set(object, FieldInfo());
168 result->fresh_entries_.Set(map.first, map_copy);
169 }
170 result->fresh_unknown_entries_.Set(object, InnerMap(zone_));
171 result->arbitrary_entries_ = empty_constant;
172 result->arbitrary_unknown_entries_ = empty_unknown;
173 } else if (Helpers::IsConstantObject(object)) {
174 // May alias with:
175 // - Constant/arbitrary objects with the any known/unknown offset
176 result->constant_entries_ = empty_constant;
177 result->constant_unknown_entries_ = empty_unknown;
178 result->arbitrary_entries_ = empty_constant;
179 result->arbitrary_unknown_entries_ = empty_unknown;
180 } else {
181 // May alias with anything. Clear the state.
182 return zone_->New<AbstractState>(zone_);
183 }
184 }
185
186 return result;
187 }
188
189 CsaLoadElimination::AbstractState const*
AddField(Node * object,Node * offset,Node * value,MachineRepresentation repr) const190 CsaLoadElimination::AbstractState::AddField(Node* object, Node* offset,
191 Node* value,
192 MachineRepresentation repr) const {
193 AbstractState* new_state = zone_->New<AbstractState>(*this);
194 IntPtrMatcher m(offset);
195 if (m.HasResolvedValue()) {
196 uint32_t offset_num = static_cast<uint32_t>(m.ResolvedValue());
197 ConstantOffsetInfos& infos = Helpers::IsFreshObject(object)
198 ? new_state->fresh_entries_
199 : Helpers::IsConstantObject(object)
200 ? new_state->constant_entries_
201 : new_state->arbitrary_entries_;
202 Update(infos, offset_num, object, FieldInfo(value, repr));
203 } else {
204 UnknownOffsetInfos& infos =
205 Helpers::IsFreshObject(object)
206 ? new_state->fresh_unknown_entries_
207 : Helpers::IsConstantObject(object)
208 ? new_state->constant_unknown_entries_
209 : new_state->arbitrary_unknown_entries_;
210 Update(infos, object, offset, FieldInfo(value, repr));
211 }
212 return new_state;
213 }
214
Lookup(Node * object,Node * offset) const215 CsaLoadElimination::FieldInfo CsaLoadElimination::AbstractState::Lookup(
216 Node* object, Node* offset) const {
217 IntPtrMatcher m(offset);
218 if (m.HasResolvedValue()) {
219 uint32_t num_offset = static_cast<uint32_t>(m.ResolvedValue());
220 const ConstantOffsetInfos& infos = Helpers::IsFreshObject(object)
221 ? fresh_entries_
222 : Helpers::IsConstantObject(object)
223 ? constant_entries_
224 : arbitrary_entries_;
225 return infos.Get(num_offset).Get(object);
226 } else {
227 const UnknownOffsetInfos& infos = Helpers::IsFreshObject(object)
228 ? fresh_unknown_entries_
229 : Helpers::IsConstantObject(object)
230 ? constant_unknown_entries_
231 : arbitrary_unknown_entries_;
232 return infos.Get(object).Get(offset);
233 }
234 }
235
236 // static
237 // Kill all elements in {infos} that overlap with an element with {offset} and
238 // size {ElementSizeInBytes(repr)}.
KillOffset(ConstantOffsetInfos & infos,uint32_t offset,MachineRepresentation repr,Zone * zone)239 void CsaLoadElimination::AbstractState::KillOffset(ConstantOffsetInfos& infos,
240 uint32_t offset,
241 MachineRepresentation repr,
242 Zone* zone) {
243 // All elements in the range [{offset}, {offset + ElementSizeInBytes(repr)})
244 // are in the killed range. We do not need to traverse the inner maps, we can
245 // just clear them.
246 for (int i = 0; i < ElementSizeInBytes(repr); i++) {
247 infos.Set(offset + i, InnerMap(zone));
248 }
249
250 // Now we have to remove all elements in earlier offsets that overlap with an
251 // element in {offset}.
252 // The earliest offset that may overlap with {offset} is
253 // {kMaximumReprSizeInBytes - 1} before.
254 uint32_t initial_offset = offset >= kMaximumReprSizeInBytes - 1
255 ? offset - (kMaximumReprSizeInBytes - 1)
256 : 0;
257 // For all offsets from {initial_offset} to {offset}, we traverse the
258 // respective inner map, and reset all elements that are large enough to
259 // overlap with {offset}.
260 for (uint32_t i = initial_offset; i < offset; i++) {
261 InnerMap map_copy(infos.Get(i));
262 for (const std::pair<Node*, FieldInfo> info : infos.Get(i)) {
263 if (info.second.representation != MachineRepresentation::kNone &&
264 ElementSizeInBytes(info.second.representation) >
265 static_cast<int>(offset - i)) {
266 map_copy.Set(info.first, {});
267 }
268 }
269 infos.Set(i, map_copy);
270 }
271 }
272
KillOffsetInFresh(Node * const object,uint32_t offset,MachineRepresentation repr)273 void CsaLoadElimination::AbstractState::KillOffsetInFresh(
274 Node* const object, uint32_t offset, MachineRepresentation repr) {
275 for (int i = 0; i < ElementSizeInBytes(repr); i++) {
276 Update(fresh_entries_, offset + i, object, {});
277 }
278 uint32_t initial_offset = offset >= kMaximumReprSizeInBytes - 1
279 ? offset - (kMaximumReprSizeInBytes - 1)
280 : 0;
281 for (uint32_t i = initial_offset; i < offset; i++) {
282 const FieldInfo& info = fresh_entries_.Get(i).Get(object);
283 if (info.representation != MachineRepresentation::kNone &&
284 ElementSizeInBytes(info.representation) >
285 static_cast<int>(offset - i)) {
286 Update(fresh_entries_, i, object, {});
287 }
288 }
289 }
290
291 // static
Print(const CsaLoadElimination::AbstractState::ConstantOffsetInfos & infos)292 void CsaLoadElimination::AbstractState::Print(
293 const CsaLoadElimination::AbstractState::ConstantOffsetInfos& infos) {
294 for (const auto outer_entry : infos) {
295 for (const auto inner_entry : outer_entry.second) {
296 Node* object = inner_entry.first;
297 uint32_t offset = outer_entry.first;
298 FieldInfo info = inner_entry.second;
299 PrintF(" #%d+#%d:%s -> #%d:%s [repr=%s]\n", object->id(), offset,
300 object->op()->mnemonic(), info.value->id(),
301 info.value->op()->mnemonic(),
302 MachineReprToString(info.representation));
303 }
304 }
305 }
306
307 // static
Print(const CsaLoadElimination::AbstractState::UnknownOffsetInfos & infos)308 void CsaLoadElimination::AbstractState::Print(
309 const CsaLoadElimination::AbstractState::UnknownOffsetInfos& infos) {
310 for (const auto outer_entry : infos) {
311 for (const auto inner_entry : outer_entry.second) {
312 Node* object = outer_entry.first;
313 Node* offset = inner_entry.first;
314 FieldInfo info = inner_entry.second;
315 PrintF(" #%d+#%d:%s -> #%d:%s [repr=%s]\n", object->id(), offset->id(),
316 object->op()->mnemonic(), info.value->id(),
317 info.value->op()->mnemonic(),
318 MachineReprToString(info.representation));
319 }
320 }
321 }
322
Print() const323 void CsaLoadElimination::AbstractState::Print() const {
324 Print(fresh_entries_);
325 Print(constant_entries_);
326 Print(arbitrary_entries_);
327 Print(fresh_unknown_entries_);
328 Print(constant_unknown_entries_);
329 Print(arbitrary_unknown_entries_);
330 }
331
ReduceLoadFromObject(Node * node,ObjectAccess const & access)332 Reduction CsaLoadElimination::ReduceLoadFromObject(Node* node,
333 ObjectAccess const& access) {
334 Node* object = NodeProperties::GetValueInput(node, 0);
335 Node* offset = NodeProperties::GetValueInput(node, 1);
336 Node* effect = NodeProperties::GetEffectInput(node);
337 AbstractState const* state = node_states_.Get(effect);
338 if (state == nullptr) return NoChange();
339
340 MachineRepresentation representation = access.machine_type.representation();
341 FieldInfo lookup_result = state->Lookup(object, offset);
342 if (!lookup_result.IsEmpty()) {
343 // Make sure we don't reuse values that were recorded with a different
344 // representation or resurrect dead {replacement} nodes.
345 MachineRepresentation from = lookup_result.representation;
346 if (Helpers::Subsumes(from, representation) &&
347 !lookup_result.value->IsDead()) {
348 Node* replacement =
349 TruncateAndExtend(lookup_result.value, from, access.machine_type);
350 ReplaceWithValue(node, replacement, effect);
351 return Replace(replacement);
352 }
353 }
354 state = state->AddField(object, offset, node, representation);
355
356 return UpdateState(node, state);
357 }
358
ReduceStoreToObject(Node * node,ObjectAccess const & access)359 Reduction CsaLoadElimination::ReduceStoreToObject(Node* node,
360 ObjectAccess const& access) {
361 Node* object = NodeProperties::GetValueInput(node, 0);
362 Node* offset = NodeProperties::GetValueInput(node, 1);
363 Node* value = NodeProperties::GetValueInput(node, 2);
364 Node* effect = NodeProperties::GetEffectInput(node);
365 AbstractState const* state = node_states_.Get(effect);
366 if (state == nullptr) return NoChange();
367
368 MachineRepresentation repr = access.machine_type.representation();
369 state = state->KillField(object, offset, repr);
370 state = state->AddField(object, offset, value, repr);
371
372 return UpdateState(node, state);
373 }
374
ReduceEffectPhi(Node * node)375 Reduction CsaLoadElimination::ReduceEffectPhi(Node* node) {
376 Node* const effect0 = NodeProperties::GetEffectInput(node, 0);
377 Node* const control = NodeProperties::GetControlInput(node);
378 AbstractState const* state0 = node_states_.Get(effect0);
379 if (state0 == nullptr) return NoChange();
380 if (control->opcode() == IrOpcode::kLoop) {
381 // Here we rely on having only reducible loops:
382 // The loop entry edge always dominates the header, so we can just take
383 // the state from the first input, and compute the loop state based on it.
384 AbstractState const* state = ComputeLoopState(node, state0);
385 return UpdateState(node, state);
386 }
387 DCHECK_EQ(IrOpcode::kMerge, control->opcode());
388
389 // Shortcut for the case when we do not know anything about some input.
390 int const input_count = node->op()->EffectInputCount();
391 for (int i = 1; i < input_count; ++i) {
392 Node* const effect = NodeProperties::GetEffectInput(node, i);
393 if (node_states_.Get(effect) == nullptr) return NoChange();
394 }
395
396 // Make a copy of the first input's state and intersect it with the state
397 // from other inputs.
398 // TODO(manoskouk): Consider computing phis for at least a subset of the
399 // state.
400 AbstractState* state = zone()->New<AbstractState>(*state0);
401 for (int i = 1; i < input_count; ++i) {
402 Node* const input = NodeProperties::GetEffectInput(node, i);
403 state->IntersectWith(node_states_.Get(input));
404 }
405 return UpdateState(node, state);
406 }
407
ReduceStart(Node * node)408 Reduction CsaLoadElimination::ReduceStart(Node* node) {
409 return UpdateState(node, empty_state());
410 }
411
ReduceCall(Node * node)412 Reduction CsaLoadElimination::ReduceCall(Node* node) {
413 Node* value = NodeProperties::GetValueInput(node, 0);
414 ExternalReferenceMatcher m(value);
415 if (m.Is(ExternalReference::check_object_type())) {
416 return PropagateInputState(node);
417 }
418 return ReduceOtherNode(node);
419 }
420
ReduceOtherNode(Node * node)421 Reduction CsaLoadElimination::ReduceOtherNode(Node* node) {
422 if (node->op()->EffectInputCount() == 1 &&
423 node->op()->EffectOutputCount() == 1) {
424 Node* const effect = NodeProperties::GetEffectInput(node);
425 AbstractState const* state = node_states_.Get(effect);
426 // If we do not know anything about the predecessor, do not propagate just
427 // yet because we will have to recompute anyway once we compute the
428 // predecessor.
429 if (state == nullptr) return NoChange();
430 // If this {node} has some uncontrolled side effects, set its state to
431 // {empty_state()}, otherwise to its input state.
432 return UpdateState(node, node->op()->HasProperty(Operator::kNoWrite)
433 ? state
434 : empty_state());
435 }
436 DCHECK_EQ(0, node->op()->EffectOutputCount());
437 return NoChange();
438 }
439
UpdateState(Node * node,AbstractState const * state)440 Reduction CsaLoadElimination::UpdateState(Node* node,
441 AbstractState const* state) {
442 AbstractState const* original = node_states_.Get(node);
443 // Only signal that the {node} has Changed, if the information about {state}
444 // has changed wrt. the {original}.
445 if (state != original) {
446 if (original == nullptr || !state->Equals(original)) {
447 node_states_.Set(node, state);
448 return Changed(node);
449 }
450 }
451 return NoChange();
452 }
453
PropagateInputState(Node * node)454 Reduction CsaLoadElimination::PropagateInputState(Node* node) {
455 Node* const effect = NodeProperties::GetEffectInput(node);
456 AbstractState const* state = node_states_.Get(effect);
457 if (state == nullptr) return NoChange();
458 return UpdateState(node, state);
459 }
460
ComputeLoopState(Node * node,AbstractState const * state) const461 CsaLoadElimination::AbstractState const* CsaLoadElimination::ComputeLoopState(
462 Node* node, AbstractState const* state) const {
463 DCHECK_EQ(node->opcode(), IrOpcode::kEffectPhi);
464 ZoneQueue<Node*> queue(zone());
465 ZoneSet<Node*> visited(zone());
466 visited.insert(node);
467 for (int i = 1; i < node->InputCount() - 1; ++i) {
468 queue.push(node->InputAt(i));
469 }
470 while (!queue.empty()) {
471 Node* const current = queue.front();
472 queue.pop();
473 if (visited.insert(current).second) {
474 if (!current->op()->HasProperty(Operator::kNoWrite)) {
475 return empty_state();
476 }
477 for (int i = 0; i < current->op()->EffectInputCount(); ++i) {
478 queue.push(NodeProperties::GetEffectInput(current, i));
479 }
480 }
481 }
482 return state;
483 }
484
TruncateAndExtend(Node * node,MachineRepresentation from,MachineType to)485 Node* CsaLoadElimination::TruncateAndExtend(Node* node,
486 MachineRepresentation from,
487 MachineType to) {
488 DCHECK(Helpers::Subsumes(from, to.representation()));
489 DCHECK_GE(ElementSizeInBytes(from), ElementSizeInBytes(to.representation()));
490
491 if (to == MachineType::Int8() || to == MachineType::Int16()) {
492 // 1st case: We want to eliminate a signed 8/16-bit load using the value
493 // from a previous subsuming load or store. Since that value might be
494 // outside 8/16-bit range, we first truncate it accordingly. Then we
495 // sign-extend the result to 32-bit.
496 DCHECK_EQ(to.semantic(), MachineSemantic::kInt32);
497 if (from == MachineRepresentation::kWord64) {
498 node = graph()->NewNode(machine()->TruncateInt64ToInt32(), node);
499 }
500 int shift = 32 - 8 * ElementSizeInBytes(to.representation());
501 return graph()->NewNode(machine()->Word32Sar(),
502 graph()->NewNode(machine()->Word32Shl(), node,
503 jsgraph()->Int32Constant(shift)),
504 jsgraph()->Int32Constant(shift));
505 } else if (to == MachineType::Uint8() || to == MachineType::Uint16()) {
506 // 2nd case: We want to eliminate an unsigned 8/16-bit load using the value
507 // from a previous subsuming load or store. Since that value might be
508 // outside 8/16-bit range, we first truncate it accordingly.
509 if (from == MachineRepresentation::kWord64) {
510 node = graph()->NewNode(machine()->TruncateInt64ToInt32(), node);
511 }
512 int mask = (1 << 8 * ElementSizeInBytes(to.representation())) - 1;
513 return graph()->NewNode(machine()->Word32And(), node,
514 jsgraph()->Int32Constant(mask));
515 } else if (from == MachineRepresentation::kWord64 &&
516 to.representation() == MachineRepresentation::kWord32) {
517 // 3rd case: Truncate 64-bits into 32-bits.
518 return graph()->NewNode(machine()->TruncateInt64ToInt32(), node);
519 } else {
520 // 4th case: No need for truncation.
521 DCHECK((from == to.representation() &&
522 (from == MachineRepresentation::kWord32 ||
523 from == MachineRepresentation::kWord64 || !IsIntegral(from))) ||
524 (IsAnyTagged(from) && IsAnyTagged(to.representation())));
525 return node;
526 }
527 }
528
common() const529 CommonOperatorBuilder* CsaLoadElimination::common() const {
530 return jsgraph()->common();
531 }
532
machine() const533 MachineOperatorBuilder* CsaLoadElimination::machine() const {
534 return jsgraph()->machine();
535 }
536
graph() const537 Graph* CsaLoadElimination::graph() const { return jsgraph()->graph(); }
538
isolate() const539 Isolate* CsaLoadElimination::isolate() const { return jsgraph()->isolate(); }
540
541 } // namespace compiler
542 } // namespace internal
543 } // namespace v8
544