Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(93)

Side by Side Diff: src/compiler/escape-analysis.cc

Issue 1554073003: [turbofan] Improve caching in escape analysis. (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Fix signedness mismatch Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/compiler/escape-analysis.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 the V8 project authors. All rights reserved. 1 // Copyright 2015 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "src/compiler/escape-analysis.h" 5 #include "src/compiler/escape-analysis.h"
6 6
7 #include "src/base/flags.h" 7 #include "src/base/flags.h"
8 #include "src/bootstrapper.h" 8 #include "src/bootstrapper.h"
9 #include "src/compilation-dependencies.h" 9 #include "src/compilation-dependencies.h"
10 #include "src/compiler/common-operator.h" 10 #include "src/compiler/common-operator.h"
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after
141 CommonOperatorBuilder* common, Node* control); 141 CommonOperatorBuilder* common, Node* control);
142 142
143 size_t size() { return info_.size(); } 143 size_t size() { return info_.size(); }
144 144
145 private: 145 private:
146 ZoneVector<VirtualObject*> info_; 146 ZoneVector<VirtualObject*> info_;
147 Node* last_changed_; 147 Node* last_changed_;
148 }; 148 };
149 149
150 150
151 class MergeCache : public ZoneObject {
152 public:
153 explicit MergeCache(Zone* zone)
154 : states_(zone), objects_(zone), fields_(zone), min_size_(SIZE_MAX) {
155 states_.reserve(4);
156 objects_.reserve(4);
157 fields_.reserve(4);
158 }
159 ZoneVector<VirtualState*>& states() { return states_; }
160 ZoneVector<VirtualObject*>& objects() { return objects_; }
161 ZoneVector<Node*>& fields() { return fields_; }
162 void Clear() {
163 states_.clear();
164 objects_.clear();
165 fields_.clear();
166 min_size_ = SIZE_MAX;
167 }
168 void push_back(VirtualState* state) {
169 states_.push_back(state);
170 min_size_ = std::min(state->size(), min_size_);
171 }
172 size_t min_size() { return min_size_; }
173
174 size_t GetVirtualObjects(NodeId id);
175 void GetVirtualObjects(VirtualState* state);
176 Node* GetFields(size_t pos);
177
178 private:
179 ZoneVector<VirtualState*> states_;
180 ZoneVector<VirtualObject*> objects_;
181 ZoneVector<Node*> fields_;
182 size_t min_size_;
183 };
184
185
186 size_t MergeCache::GetVirtualObjects(NodeId id) {
187 objects_.clear();
188 DCHECK_GT(states_.size(), 0u);
189 size_t min = SIZE_MAX;
190 for (size_t i = 0; i < states_.size(); ++i) {
Benedikt Meurer 2016/01/05 05:00:01 Nit: for (VirtualState* const state : states_) { .
sigurds 2016/01/05 09:52:53 Done.
191 if (VirtualObject* obj = states_[i]->GetVirtualObject(id)) {
192 objects_.push_back(obj);
193 min = std::min(obj->field_count(), min);
194 }
195 }
196 return min;
197 }
198
199
200 void MergeCache::GetVirtualObjects(VirtualState* state) {
Benedikt Meurer 2016/01/05 05:00:01 This name is weird. This method doesn't really "ge
sigurds 2016/01/05 09:52:53 Done.
201 objects_.clear();
202 for (size_t i = 0; i < fields_.size(); ++i) {
Benedikt Meurer 2016/01/05 05:00:01 Nit: for (Node* const field : fields_) { ... }
sigurds 2016/01/05 09:52:53 Done.
203 if (VirtualObject* obj = state->GetVirtualObject(fields_[i])) {
204 objects_.push_back(obj);
205 }
206 }
207 }
208
209
210 Node* MergeCache::GetFields(size_t pos) {
211 fields_.clear();
212 Node* rep = objects_.front()->GetField(pos);
213 for (VirtualObject* obj : objects_) {
214 Node* field = obj->GetField(pos);
215 if (field) {
216 fields_.push_back(field);
217 }
218 if (field != rep) {
219 rep = nullptr;
220 }
221 }
222 return rep;
223 }
224
225
151 VirtualState::VirtualState(Zone* zone, size_t size) 226 VirtualState::VirtualState(Zone* zone, size_t size)
152 : info_(zone), last_changed_(nullptr) { 227 : info_(zone), last_changed_(nullptr) {
153 info_.resize(size); 228 info_.resize(size);
154 } 229 }
155 230
156 231
157 VirtualState::VirtualState(const VirtualState& state) 232 VirtualState::VirtualState(const VirtualState& state)
158 : info_(state.info_.get_allocator().zone()), 233 : info_(state.info_.get_allocator().zone()),
159 last_changed_(state.last_changed_) { 234 last_changed_(state.last_changed_) {
160 info_.resize(state.info_.size()); 235 info_.resize(state.info_.size());
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
245 } 320 }
246 321
247 changed = ls->UpdateFrom(*rs) || changed; 322 changed = ls->UpdateFrom(*rs) || changed;
248 } 323 }
249 return false; 324 return false;
250 } 325 }
251 326
252 327
253 namespace { 328 namespace {
254 329
255 size_t min_size(ZoneVector<VirtualState*>& states) {
256 size_t min = SIZE_MAX;
257 for (VirtualState* state : states) {
258 min = std::min(state->size(), min);
259 }
260 return min;
261 }
262
263
264 size_t min_field_count(ZoneVector<VirtualObject*>& objs) {
265 size_t min = SIZE_MAX;
266 for (VirtualObject* obj : objs) {
267 min = std::min(obj->field_count(), min);
268 }
269 return min;
270 }
271
272
273 void GetVirtualObjects(ZoneVector<VirtualState*> states,
274 ZoneVector<VirtualObject*>& objs, NodeId id) {
275 objs.clear();
276 for (VirtualState* state : states) {
277 if (VirtualObject* obj = state->GetVirtualObject(id)) {
278 objs.push_back(obj);
279 }
280 }
281 }
282
283
284 void GetVirtualObjects(VirtualState* state, ZoneVector<Node*> nodes,
285 ZoneVector<VirtualObject*>& objs) {
286 objs.clear();
287 for (Node* node : nodes) {
288 if (VirtualObject* obj = state->GetVirtualObject(node)) {
289 objs.push_back(obj);
290 }
291 }
292 }
293
294
295 Node* GetFieldIfSame(size_t pos, ZoneVector<VirtualObject*>& objs) {
296 Node* rep = objs.front()->GetField(pos);
297 for (VirtualObject* obj : objs) {
298 if (obj->GetField(pos) != rep) {
299 return nullptr;
300 }
301 }
302 return rep;
303 }
304
305
306 void GetFields(ZoneVector<VirtualObject*>& objs, ZoneVector<Node*>& fields,
307 size_t pos) {
308 fields.clear();
309 for (VirtualObject* obj : objs) {
310 if (Node* field = obj->GetField(pos)) {
311 fields.push_back(field);
312 }
313 }
314 }
315
316
317 bool IsEquivalentPhi(Node* node1, Node* node2) { 330 bool IsEquivalentPhi(Node* node1, Node* node2) {
318 if (node1 == node2) return true; 331 if (node1 == node2) return true;
319 if (node1->opcode() != IrOpcode::kPhi || node2->opcode() != IrOpcode::kPhi || 332 if (node1->opcode() != IrOpcode::kPhi || node2->opcode() != IrOpcode::kPhi ||
320 node1->op()->ValueInputCount() != node2->op()->ValueInputCount()) { 333 node1->op()->ValueInputCount() != node2->op()->ValueInputCount()) {
321 return false; 334 return false;
322 } 335 }
323 for (int i = 0; i < node1->op()->ValueInputCount(); ++i) { 336 for (int i = 0; i < node1->op()->ValueInputCount(); ++i) {
324 Node* input1 = NodeProperties::GetValueInput(node1, i); 337 Node* input1 = NodeProperties::GetValueInput(node1, i);
325 Node* input2 = NodeProperties::GetValueInput(node2, i); 338 Node* input2 = NodeProperties::GetValueInput(node2, i);
326 if (!IsEquivalentPhi(input1, input2)) { 339 if (!IsEquivalentPhi(input1, input2)) {
(...skipping 29 matching lines...) Expand all
356 } 369 }
357 } 370 }
358 return rep; 371 return rep;
359 } 372 }
360 373
361 374
362 bool VirtualState::MergeFrom(MergeCache* cache, Zone* zone, Graph* graph, 375 bool VirtualState::MergeFrom(MergeCache* cache, Zone* zone, Graph* graph,
363 CommonOperatorBuilder* common, Node* control) { 376 CommonOperatorBuilder* common, Node* control) {
364 DCHECK_GT(cache->states().size(), 0u); 377 DCHECK_GT(cache->states().size(), 0u);
365 bool changed = false; 378 bool changed = false;
366 for (NodeId id = 0; id < min_size(cache->states()); ++id) { 379 for (NodeId id = 0; id < cache->min_size(); ++id) {
367 GetVirtualObjects(cache->states(), cache->objects(), id); 380 size_t fields = cache->GetVirtualObjects(id);
368 if (cache->objects().size() == cache->states().size()) { 381 if (cache->objects().size() == cache->states().size()) {
369 // Don't process linked objects. 382 // Don't process linked objects.
370 if (cache->objects()[0]->id() != id) continue; 383 if (cache->objects()[0]->id() != id) continue;
371 if (FLAG_trace_turbo_escape) { 384 if (FLAG_trace_turbo_escape) {
372 PrintF(" Merging virtual objects of #%d\n", id); 385 PrintF(" Merging virtual objects of #%d\n", id);
373 } 386 }
374 VirtualObject* mergeObject = GetOrCreateTrackedVirtualObject(id, zone); 387 VirtualObject* mergeObject = GetOrCreateTrackedVirtualObject(id, zone);
375 size_t fields = min_field_count(cache->objects());
376 changed = mergeObject->ResizeFields(fields) || changed; 388 changed = mergeObject->ResizeFields(fields) || changed;
377 for (size_t i = 0; i < fields; ++i) { 389 for (size_t i = 0; i < fields; ++i) {
378 if (Node* field = GetFieldIfSame(i, cache->objects())) { 390 if (Node* field = cache->GetFields(i)) {
379 changed = mergeObject->SetField(i, field) || changed; 391 changed = mergeObject->SetField(i, field) || changed;
380 if (FLAG_trace_turbo_escape) { 392 if (FLAG_trace_turbo_escape) {
381 PrintF(" Field %zu agree on rep #%d\n", i, field->id()); 393 PrintF(" Field %zu agree on rep #%d\n", i, field->id());
382 } 394 }
383 } else { 395 } else {
384 GetFields(cache->objects(), cache->fields(), i); 396 int value_input_count = static_cast<int>(cache->fields().size());
385 if (cache->fields().size() == cache->objects().size()) { 397 if (cache->fields().size() == cache->objects().size()) {
386 Node* rep = mergeObject->GetField(i); 398 Node* rep = mergeObject->GetField(i);
387 if (!rep || !mergeObject->IsCreatedPhi(i)) { 399 if (!rep || !mergeObject->IsCreatedPhi(i)) {
388 cache->fields().push_back(control); 400 cache->fields().push_back(control);
389 Node* phi = 401 Node* phi = graph->NewNode(
390 graph->NewNode(common->Phi(MachineRepresentation::kTagged, 2), 402 common->Phi(MachineRepresentation::kTagged,
391 static_cast<int>(cache->fields().size()), 403 value_input_count),
392 &cache->fields().front()); 404 value_input_count + 1, &cache->fields().front());
393 mergeObject->SetField(i, phi, true); 405 mergeObject->SetField(i, phi, true);
394 if (FLAG_trace_turbo_escape) { 406 if (FLAG_trace_turbo_escape) {
395 PrintF(" Creating Phi #%d as merge of", phi->id()); 407 PrintF(" Creating Phi #%d as merge of", phi->id());
396 for (size_t i = 0; i + 1 < cache->fields().size(); i++) { 408 for (int i = 0; i < value_input_count; i++) {
397 PrintF(" #%d (%s)", cache->fields()[i]->id(), 409 PrintF(" #%d (%s)", cache->fields()[i]->id(),
398 cache->fields()[i]->op()->mnemonic()); 410 cache->fields()[i]->op()->mnemonic());
399 } 411 }
400 PrintF("\n"); 412 PrintF("\n");
401 } 413 }
402 changed = true; 414 changed = true;
403 } else { 415 } else {
404 DCHECK(rep->opcode() == IrOpcode::kPhi); 416 DCHECK(rep->opcode() == IrOpcode::kPhi);
405 for (size_t n = 0; n < cache->fields().size(); ++n) { 417 for (int n = 0; n < value_input_count; ++n) {
406 Node* old = 418 if (n < rep->op()->ValueInputCount()) {
407 NodeProperties::GetValueInput(rep, static_cast<int>(n)); 419 Node* old = NodeProperties::GetValueInput(rep, n);
408 if (old != cache->fields()[n]) { 420 if (old != cache->fields()[n]) {
421 changed = true;
422 NodeProperties::ReplaceValueInput(rep, cache->fields()[n],
423 n);
424 }
425 } else {
409 changed = true; 426 changed = true;
410 NodeProperties::ReplaceValueInput(rep, cache->fields()[n], 427 rep->InsertInput(graph->zone(), n, cache->fields()[n]);
411 static_cast<int>(n));
412 } 428 }
413 } 429 }
430 if (rep->op()->ValueInputCount() != value_input_count) {
431 PrintF(" Widening Phi #%d of arity %d to %d", rep->id(),
432 rep->op()->ValueInputCount(), value_input_count);
433 NodeProperties::ChangeOp(
434 rep, common->Phi(MachineRepresentation::kTagged,
435 value_input_count));
436 }
414 } 437 }
415 } else { 438 } else {
416 changed = mergeObject->SetField(i, nullptr) || changed; 439 changed = mergeObject->SetField(i, nullptr) || changed;
417 } 440 }
418 } 441 }
419 } 442 }
420 443
421 } else { 444 } else {
422 SetVirtualObject(id, nullptr); 445 SetVirtualObject(id, nullptr);
423 } 446 }
424 } 447 }
425 // Update linked objects. 448 // Update linked objects.
426 for (NodeId id = 0; id < min_size(cache->states()); ++id) { 449 for (NodeId id = 0; id < cache->min_size(); ++id) {
427 GetVirtualObjects(cache->states(), cache->objects(), id); 450 if (VirtualObject* obj = cache->states().front()->GetVirtualObject(id)) {
428 if (cache->objects().size() == cache->states().size()) { 451 if (obj->id() != id) {
429 if (cache->objects()[0]->id() != id) { 452 SetVirtualObject(id, GetVirtualObject(obj->id()));
430 SetVirtualObject(id, GetVirtualObject(cache->objects()[0]->id()));
431 } 453 }
432 } 454 }
433 } 455 }
434 return changed; 456 return changed;
435 } 457 }
436 458
437 459
438 // ------------------------------EscapeStatusAnalysis--------------------------- 460 // ------------------------------EscapeStatusAnalysis---------------------------
439 461
440 462
(...skipping 265 matching lines...) Expand 10 before | Expand all | Expand 10 after
706 728
707 729
708 EscapeAnalysis::EscapeAnalysis(Graph* graph, CommonOperatorBuilder* common, 730 EscapeAnalysis::EscapeAnalysis(Graph* graph, CommonOperatorBuilder* common,
709 Zone* zone) 731 Zone* zone)
710 : graph_(graph), 732 : graph_(graph),
711 common_(common), 733 common_(common),
712 zone_(zone), 734 zone_(zone),
713 virtual_states_(zone), 735 virtual_states_(zone),
714 replacements_(zone), 736 replacements_(zone),
715 escape_status_(this, graph, zone), 737 escape_status_(this, graph, zone),
716 cache_(zone) {} 738 cache_(new (zone) MergeCache(zone)) {}
717 739
718 740
719 EscapeAnalysis::~EscapeAnalysis() {} 741 EscapeAnalysis::~EscapeAnalysis() {}
720 742
721 743
722 void EscapeAnalysis::Run() { 744 void EscapeAnalysis::Run() {
723 replacements_.resize(graph()->NodeCount()); 745 replacements_.resize(graph()->NodeCount());
724 RunObjectAnalysis(); 746 RunObjectAnalysis();
725 escape_status_.Run(); 747 escape_status_.Run();
726 } 748 }
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after
926 virtual_states_[node->id()] = mergeState; 948 virtual_states_[node->id()] = mergeState;
927 changed = true; 949 changed = true;
928 if (FLAG_trace_turbo_escape) { 950 if (FLAG_trace_turbo_escape) {
929 PrintF("Effect Phi #%d got new states map %p.\n", node->id(), 951 PrintF("Effect Phi #%d got new states map %p.\n", node->id(),
930 static_cast<void*>(mergeState)); 952 static_cast<void*>(mergeState));
931 } 953 }
932 } else if (mergeState->GetLastChanged() != node) { 954 } else if (mergeState->GetLastChanged() != node) {
933 changed = true; 955 changed = true;
934 } 956 }
935 957
936 cache_.Clear(); 958 cache_->Clear();
937 959
938 if (FLAG_trace_turbo_escape) { 960 if (FLAG_trace_turbo_escape) {
939 PrintF("At Effect Phi #%d, merging states into %p:", node->id(), 961 PrintF("At Effect Phi #%d, merging states into %p:", node->id(),
940 static_cast<void*>(mergeState)); 962 static_cast<void*>(mergeState));
941 } 963 }
942 964
943 for (int i = 0; i < node->op()->EffectInputCount(); ++i) { 965 for (int i = 0; i < node->op()->EffectInputCount(); ++i) {
944 Node* input = NodeProperties::GetEffectInput(node, i); 966 Node* input = NodeProperties::GetEffectInput(node, i);
945 VirtualState* state = virtual_states_[input->id()]; 967 VirtualState* state = virtual_states_[input->id()];
946 if (state) { 968 if (state) {
947 cache_.states().push_back(state); 969 cache_->push_back(state);
948 } 970 }
949 if (FLAG_trace_turbo_escape) { 971 if (FLAG_trace_turbo_escape) {
950 PrintF(" %p (from %d %s)", static_cast<void*>(state), input->id(), 972 PrintF(" %p (from %d %s)", static_cast<void*>(state), input->id(),
951 input->op()->mnemonic()); 973 input->op()->mnemonic());
952 } 974 }
953 } 975 }
954 if (FLAG_trace_turbo_escape) { 976 if (FLAG_trace_turbo_escape) {
955 PrintF("\n"); 977 PrintF("\n");
956 } 978 }
957 979
958 if (cache_.states().size() == 0) { 980 if (cache_->states().size() == 0) {
959 return changed; 981 return changed;
960 } 982 }
961 983
962 changed = mergeState->MergeFrom(&cache_, zone(), graph(), common(), 984 changed = mergeState->MergeFrom(cache_, zone(), graph(), common(),
963 NodeProperties::GetControlInput(node)) || 985 NodeProperties::GetControlInput(node)) ||
964 changed; 986 changed;
965 987
966 if (FLAG_trace_turbo_escape) { 988 if (FLAG_trace_turbo_escape) {
967 PrintF("Merge %s the node.\n", changed ? "changed" : "did not change"); 989 PrintF("Merge %s the node.\n", changed ? "changed" : "did not change");
968 } 990 }
969 991
970 if (changed) { 992 if (changed) {
971 mergeState->LastChangedAt(node); 993 mergeState->LastChangedAt(node);
972 escape_status_.Resize(); 994 escape_status_.Resize();
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after
1119 return OpParameter<FieldAccess>(node).offset / kPointerSize; 1141 return OpParameter<FieldAccess>(node).offset / kPointerSize;
1120 } 1142 }
1121 1143
1122 1144
1123 void EscapeAnalysis::ProcessLoadFromPhi(int offset, Node* from, Node* node, 1145 void EscapeAnalysis::ProcessLoadFromPhi(int offset, Node* from, Node* node,
1124 VirtualState* state) { 1146 VirtualState* state) {
1125 if (FLAG_trace_turbo_escape) { 1147 if (FLAG_trace_turbo_escape) {
1126 PrintF("Load #%d from phi #%d", node->id(), from->id()); 1148 PrintF("Load #%d from phi #%d", node->id(), from->id());
1127 } 1149 }
1128 1150
1129 ZoneVector<Node*> inputs(zone()); 1151 cache_->fields().clear();
1130 for (int i = 0; i < node->op()->ValueInputCount(); ++i) { 1152 for (int i = 0; i < node->op()->ValueInputCount(); ++i) {
1131 Node* input = NodeProperties::GetValueInput(node, i); 1153 Node* input = NodeProperties::GetValueInput(node, i);
1132 inputs.push_back(input); 1154 cache_->fields().push_back(input);
1133 } 1155 }
1134 1156
1135 GetVirtualObjects(state, inputs, cache_.objects()); 1157 cache_->GetVirtualObjects(state);
1136 if (cache_.objects().size() == inputs.size()) { 1158 if (cache_->objects().size() == cache_->fields().size()) {
1137 GetFields(cache_.objects(), cache_.fields(), offset); 1159 cache_->GetFields(offset);
1138 if (cache_.fields().size() == cache_.objects().size()) { 1160 if (cache_->fields().size() == cache_->objects().size()) {
1139 Node* rep = replacement(node); 1161 Node* rep = replacement(node);
1140 if (!rep || !IsEquivalentPhi(rep, cache_.fields())) { 1162 if (!rep || !IsEquivalentPhi(rep, cache_->fields())) {
1141 cache_.fields().push_back(NodeProperties::GetControlInput(from)); 1163 int value_input_count = static_cast<int>(cache_->fields().size());
1164 cache_->fields().push_back(NodeProperties::GetControlInput(from));
1142 Node* phi = graph()->NewNode( 1165 Node* phi = graph()->NewNode(
1143 common()->Phi(MachineRepresentation::kTagged, 2), 1166 common()->Phi(MachineRepresentation::kTagged, value_input_count),
1144 static_cast<int>(cache_.fields().size()), &cache_.fields().front()); 1167 value_input_count + 1, &cache_->fields().front());
1145 escape_status_.Resize(); 1168 escape_status_.Resize();
1146 SetReplacement(node, phi); 1169 SetReplacement(node, phi);
1147 state->LastChangedAt(node); 1170 state->LastChangedAt(node);
1148 if (FLAG_trace_turbo_escape) { 1171 if (FLAG_trace_turbo_escape) {
1149 PrintF(" got phi created.\n"); 1172 PrintF(" got phi created.\n");
1150 } 1173 }
1151 } else if (FLAG_trace_turbo_escape) { 1174 } else if (FLAG_trace_turbo_escape) {
1152 PrintF(" has already phi #%d.\n", rep->id()); 1175 PrintF(" has already phi #%d.\n", rep->id());
1153 } 1176 }
1154 } else if (FLAG_trace_turbo_escape) { 1177 } else if (FLAG_trace_turbo_escape) {
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after
1315 } 1338 }
1316 } 1339 }
1317 for (size_t n = 0; n < object_states.size(); n++) { 1340 for (size_t n = 0; n < object_states.size(); n++) {
1318 DebugPrintState(object_states[n]); 1341 DebugPrintState(object_states[n]);
1319 } 1342 }
1320 } 1343 }
1321 1344
1322 } // namespace compiler 1345 } // namespace compiler
1323 } // namespace internal 1346 } // namespace internal
1324 } // namespace v8 1347 } // namespace v8
OLDNEW
« no previous file with comments | « src/compiler/escape-analysis.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698