Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 #include "vm/flow_graph_optimizer.h" | 5 #include "vm/flow_graph_optimizer.h" |
| 6 | 6 |
| 7 #include "vm/bit_vector.h" | 7 #include "vm/bit_vector.h" |
| 8 #include "vm/cha.h" | 8 #include "vm/cha.h" |
| 9 #include "vm/cpu.h" | 9 #include "vm/cpu.h" |
| 10 #include "vm/dart_entry.h" | 10 #include "vm/dart_entry.h" |
| (...skipping 5069 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 5080 // | 5080 // |
| 5081 // - for fields | 5081 // - for fields |
| 5082 // - *.f, *.@offs - field inside some object; | 5082 // - *.f, *.@offs - field inside some object; |
| 5083 // - X.f, X.@offs - field inside an allocated object X; | 5083 // - X.f, X.@offs - field inside an allocated object X; |
| 5084 // - for indexed accesses | 5084 // - for indexed accesses |
| 5085 // - *[*] - non-constant index inside some object; | 5085 // - *[*] - non-constant index inside some object; |
| 5086 // - *[C] - constant index inside some object; | 5086 // - *[C] - constant index inside some object; |
| 5087 // - X[*] - non-constant index inside an allocated object X; | 5087 // - X[*] - non-constant index inside an allocated object X; |
| 5088 // - X[C] - constant index inside an allocated object X. | 5088 // - X[C] - constant index inside an allocated object X. |
| 5089 // | 5089 // |
| 5090 // Constant indexed places are divided into two subcategories: | |
| 5091 // | |
| 5092 // - Access to homogeneous array-like objects: Array, ImmutableArray, | |
| 5093 // OneByteString, TwoByteString. These objects can only be accessed | |
| 5094 // on element by element basis with all elements having the same size. | |
| 5095 // This means X[C] aliases X[K] if and only if C === K. | |
| 5096 // - TypedData accesses. TypedData allow to read one of the primitive | |
| 5097 // data types at the given byte offset. The only guarantee is that byte | |
| 5098 // offset is always aligned by the data type size. We write these accesses | |
|
Florian Schneider
2015/01/26 13:30:39
Do you mean data type size of the underlying array
Vyacheslav Egorov (Google)
2015/01/26 14:57:16
I was talking about element size (access size). I
| |
| 5099 // as X[C|S], where C is constant byte offset and S is size of the data | |
| 5100 // type. Obviously X[C|S] and X[K|U] alias if and only if either | |
| 5101 // C = RoundDown(K, S) or K = RoundDown(C, U). | |
| 5102 // | |
| 5090 // Separating allocations from other objects improves precision of the | 5103 // Separating allocations from other objects improves precision of the |
| 5091 // load forwarding pass because of the following two properties: | 5104 // load forwarding pass because of the following two properties: |
| 5092 // | 5105 // |
| 5093 // - if X can be proven to have no aliases itself (i.e. there is no other SSA | 5106 // - if X can be proven to have no aliases itself (i.e. there is no other SSA |
| 5094 // variable that points to X) then no place inside X can be aliased with any | 5107 // variable that points to X) then no place inside X can be aliased with any |
| 5095 // wildcard dependent place (*.f, *.@offs, *[*], *[C]); | 5108 // wildcard dependent place (*.f, *.@offs, *[*], *[C]); |
| 5096 // - given allocations X and Y no place inside X can be aliased with any place | 5109 // - given allocations X and Y no place inside X can be aliased with any place |
| 5097 // inside Y even if any of them or both escape. | 5110 // inside Y even if any of them or both escape. |
| 5098 // | 5111 // |
| 5099 // It important to realize that single place can belong to multiple aliases. | 5112 // It important to realize that single place can belong to multiple aliases. |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 5115 // being accessed and offset to the field. | 5128 // being accessed and offset to the field. |
| 5116 kVMField, | 5129 kVMField, |
| 5117 | 5130 |
| 5118 // Indexed location with a non-constant index. | 5131 // Indexed location with a non-constant index. |
| 5119 kIndexed, | 5132 kIndexed, |
| 5120 | 5133 |
| 5121 // Indexed location with a constant index. | 5134 // Indexed location with a constant index. |
| 5122 kConstantIndexed, | 5135 kConstantIndexed, |
| 5123 }; | 5136 }; |
| 5124 | 5137 |
| 5138 // Size of the element accessed by constant index. Size is only important | |
| 5139 // for TypedData because those accesses can alias even when constant indexes | |
| 5140 // are not the same: X[0|4] aliases X[0|2] and X[2|2]. | |
| 5141 enum ElementSize { | |
| 5142 // If indexed access is not a TypedData access then element size is not | |
| 5143 // important because there is only a single possible access size depending | |
| 5144 // on the receiver - X[C] aliases X[K] if and only if C == K. | |
| 5145 // This is the size set for Array, ImmutableArray, OneByteString and | |
| 5146 // TwoByteString accesses. | |
| 5147 kNoSize, | |
| 5148 | |
| 5149 // 1 byte (Int8List, Uint8List, Uint8ClampedList). | |
| 5150 kInt8, | |
| 5151 | |
| 5152 // 2 bytes (Int16List, Uint16List). | |
| 5153 kInt16, | |
| 5154 | |
| 5155 // 4 bytes (Int32List, Uint32List, Float32List). | |
| 5156 kInt32, | |
| 5157 | |
| 5158 // 8 bytes (Int64List, Uint64List, Float64List). | |
| 5159 kInt64, | |
| 5160 | |
| 5161 // 16 bytes (Int32x4List, Float32x4List, Float64x2List). | |
| 5162 kInt128, | |
| 5163 | |
| 5164 kLargestElementSize = kInt128, | |
| 5165 }; | |
| 5166 | |
| 5125 Place(const Place& other) | 5167 Place(const Place& other) |
| 5126 : ValueObject(), | 5168 : ValueObject(), |
| 5127 kind_(other.kind_), | 5169 flags_(other.flags_), |
| 5128 representation_(other.representation_), | |
| 5129 instance_(other.instance_), | 5170 instance_(other.instance_), |
| 5130 raw_selector_(other.raw_selector_), | 5171 raw_selector_(other.raw_selector_), |
| 5131 id_(other.id_) { | 5172 id_(other.id_) { |
| 5132 } | 5173 } |
| 5133 | 5174 |
| 5134 // Construct a place from instruction if instruction accesses any place. | 5175 // Construct a place from instruction if instruction accesses any place. |
| 5135 // Otherwise constructs kNone place. | 5176 // Otherwise constructs kNone place. |
| 5136 Place(Instruction* instr, bool* is_load, bool* is_store) | 5177 Place(Instruction* instr, bool* is_load, bool* is_store) |
| 5137 : kind_(kNone), | 5178 : flags_(0), |
| 5138 representation_(kNoRepresentation), | |
| 5139 instance_(NULL), | 5179 instance_(NULL), |
| 5140 raw_selector_(0), | 5180 raw_selector_(0), |
| 5141 id_(0) { | 5181 id_(0) { |
| 5142 switch (instr->tag()) { | 5182 switch (instr->tag()) { |
| 5143 case Instruction::kLoadField: { | 5183 case Instruction::kLoadField: { |
| 5144 LoadFieldInstr* load_field = instr->AsLoadField(); | 5184 LoadFieldInstr* load_field = instr->AsLoadField(); |
| 5145 representation_ = load_field->representation(); | 5185 set_representation(load_field->representation()); |
| 5146 instance_ = load_field->instance()->definition()->OriginalDefinition(); | 5186 instance_ = load_field->instance()->definition()->OriginalDefinition(); |
| 5147 if (load_field->field() != NULL) { | 5187 if (load_field->field() != NULL) { |
| 5148 kind_ = kField; | 5188 set_kind(kField); |
| 5149 field_ = load_field->field(); | 5189 field_ = load_field->field(); |
| 5150 } else { | 5190 } else { |
| 5151 kind_ = kVMField; | 5191 set_kind(kVMField); |
| 5152 offset_in_bytes_ = load_field->offset_in_bytes(); | 5192 offset_in_bytes_ = load_field->offset_in_bytes(); |
| 5153 } | 5193 } |
| 5154 *is_load = true; | 5194 *is_load = true; |
| 5155 break; | 5195 break; |
| 5156 } | 5196 } |
| 5157 | 5197 |
| 5158 case Instruction::kStoreInstanceField: { | 5198 case Instruction::kStoreInstanceField: { |
| 5159 StoreInstanceFieldInstr* store = | 5199 StoreInstanceFieldInstr* store = |
| 5160 instr->AsStoreInstanceField(); | 5200 instr->AsStoreInstanceField(); |
| 5161 representation_ = store->RequiredInputRepresentation( | 5201 set_representation(store->RequiredInputRepresentation( |
| 5162 StoreInstanceFieldInstr::kValuePos); | 5202 StoreInstanceFieldInstr::kValuePos)); |
| 5163 instance_ = store->instance()->definition()->OriginalDefinition(); | 5203 instance_ = store->instance()->definition()->OriginalDefinition(); |
| 5164 if (!store->field().IsNull()) { | 5204 if (!store->field().IsNull()) { |
| 5165 kind_ = kField; | 5205 set_kind(kField); |
| 5166 field_ = &store->field(); | 5206 field_ = &store->field(); |
| 5167 } else { | 5207 } else { |
| 5168 kind_ = kVMField; | 5208 set_kind(kVMField); |
| 5169 offset_in_bytes_ = store->offset_in_bytes(); | 5209 offset_in_bytes_ = store->offset_in_bytes(); |
| 5170 } | 5210 } |
| 5171 *is_store = true; | 5211 *is_store = true; |
| 5172 break; | 5212 break; |
| 5173 } | 5213 } |
| 5174 | 5214 |
| 5175 case Instruction::kLoadStaticField: | 5215 case Instruction::kLoadStaticField: |
| 5176 kind_ = kField; | 5216 set_kind(kField); |
| 5177 representation_ = instr->AsLoadStaticField()->representation(); | 5217 set_representation(instr->AsLoadStaticField()->representation()); |
| 5178 field_ = &instr->AsLoadStaticField()->StaticField(); | 5218 field_ = &instr->AsLoadStaticField()->StaticField(); |
| 5179 *is_load = true; | 5219 *is_load = true; |
| 5180 break; | 5220 break; |
| 5181 | 5221 |
| 5182 case Instruction::kStoreStaticField: | 5222 case Instruction::kStoreStaticField: |
| 5183 kind_ = kField; | 5223 set_kind(kField); |
| 5184 representation_ = instr->AsStoreStaticField()-> | 5224 set_representation(instr->AsStoreStaticField()-> |
| 5185 RequiredInputRepresentation(StoreStaticFieldInstr::kValuePos); | 5225 RequiredInputRepresentation(StoreStaticFieldInstr::kValuePos)); |
| 5186 field_ = &instr->AsStoreStaticField()->field(); | 5226 field_ = &instr->AsStoreStaticField()->field(); |
| 5187 *is_store = true; | 5227 *is_store = true; |
| 5188 break; | 5228 break; |
| 5189 | 5229 |
| 5190 case Instruction::kLoadIndexed: { | 5230 case Instruction::kLoadIndexed: { |
| 5191 LoadIndexedInstr* load_indexed = instr->AsLoadIndexed(); | 5231 LoadIndexedInstr* load_indexed = instr->AsLoadIndexed(); |
| 5192 representation_ = load_indexed->representation(); | 5232 set_representation(load_indexed->representation()); |
| 5193 instance_ = load_indexed->array()->definition()->OriginalDefinition(); | 5233 instance_ = load_indexed->array()->definition()->OriginalDefinition(); |
| 5194 SetIndex(load_indexed->index()->definition()); | 5234 SetIndex(load_indexed->index()->definition(), |
| 5235 load_indexed->index_scale(), | |
| 5236 load_indexed->class_id()); | |
| 5195 *is_load = true; | 5237 *is_load = true; |
| 5196 break; | 5238 break; |
| 5197 } | 5239 } |
| 5198 | 5240 |
| 5199 case Instruction::kStoreIndexed: { | 5241 case Instruction::kStoreIndexed: { |
| 5200 StoreIndexedInstr* store_indexed = instr->AsStoreIndexed(); | 5242 StoreIndexedInstr* store_indexed = instr->AsStoreIndexed(); |
| 5201 representation_ = store_indexed-> | 5243 set_representation(store_indexed-> |
| 5202 RequiredInputRepresentation(StoreIndexedInstr::kValuePos); | 5244 RequiredInputRepresentation(StoreIndexedInstr::kValuePos)); |
| 5203 instance_ = store_indexed->array()->definition()->OriginalDefinition(); | 5245 instance_ = store_indexed->array()->definition()->OriginalDefinition(); |
| 5204 SetIndex(store_indexed->index()->definition()); | 5246 SetIndex(store_indexed->index()->definition(), |
| 5247 store_indexed->index_scale(), | |
| 5248 store_indexed->class_id()); | |
| 5205 *is_store = true; | 5249 *is_store = true; |
| 5206 break; | 5250 break; |
| 5207 } | 5251 } |
| 5208 | 5252 |
| 5209 default: | 5253 default: |
| 5210 break; | 5254 break; |
| 5211 } | 5255 } |
| 5212 } | 5256 } |
| 5213 | 5257 |
| 5214 // Create object representing *[*] alias. | 5258 // Create object representing *[*] alias. |
| 5215 static Place* CreateAnyInstanceAnyIndexAlias(Isolate* isolate, | 5259 static Place* CreateAnyInstanceAnyIndexAlias(Isolate* isolate, |
| 5216 intptr_t id) { | 5260 intptr_t id) { |
| 5217 return Wrap(isolate, Place(kIndexed, NULL, 0), id); | 5261 return Wrap(isolate, Place( |
| 5262 EncodeFlags(kIndexed, kNoRepresentation, kNoSize), | |
| 5263 NULL, | |
| 5264 0), id); | |
| 5218 } | 5265 } |
| 5219 | 5266 |
| 5220 // Return least generic alias for this place. Given that aliases are | 5267 // Return least generic alias for this place. Given that aliases are |
| 5221 // essentially sets of places we define least generic alias as a smallest | 5268 // essentially sets of places we define least generic alias as a smallest |
| 5222 // alias that contains this place. | 5269 // alias that contains this place. |
| 5223 // | 5270 // |
| 5224 // We obtain such alias by a simple transformation: | 5271 // We obtain such alias by a simple transformation: |
| 5225 // | 5272 // |
| 5226 // - for places that depend on an instance X.f, X.@offs, X[i], X[C] | 5273 // - for places that depend on an instance X.f, X.@offs, X[i], X[C] |
| 5227 // we drop X if X is not an allocation because in this case X does not | 5274 // we drop X if X is not an allocation because in this case X does not |
| 5228 // posess an identity obtaining aliases *.f, *.@offs, *[i] and *[C] | 5275 // posess an identity obtaining aliases *.f, *.@offs, *[i] and *[C] |
| 5229 // respectively; | 5276 // respectively; |
| 5230 // - for non-constant indexed places X[i] we drop information about the | 5277 // - for non-constant indexed places X[i] we drop information about the |
| 5231 // index obtaining alias X[*]. | 5278 // index obtaining alias X[*]. |
| 5279 // - we drop information about representation, but keep element size | |
| 5280 // if any. | |
| 5232 // | 5281 // |
| 5233 Place ToAlias() const { | 5282 Place ToAlias() const { |
| 5234 return Place( | 5283 return Place( |
| 5235 kind_, | 5284 RepresentationBits::update(kNoRepresentation, flags_), |
| 5236 (DependsOnInstance() && IsAllocation(instance())) ? instance() : NULL, | 5285 (DependsOnInstance() && IsAllocation(instance())) ? instance() : NULL, |
| 5237 (kind() == kIndexed) ? 0 : raw_selector_); | 5286 (kind() == kIndexed) ? 0 : raw_selector_); |
| 5238 } | 5287 } |
| 5239 | 5288 |
| 5240 bool DependsOnInstance() const { | 5289 bool DependsOnInstance() const { |
| 5241 switch (kind()) { | 5290 switch (kind()) { |
| 5242 case kField: | 5291 case kField: |
| 5243 case kVMField: | 5292 case kVMField: |
| 5244 case kIndexed: | 5293 case kIndexed: |
| 5245 case kConstantIndexed: | 5294 case kConstantIndexed: |
| 5246 return true; | 5295 return true; |
| 5247 | 5296 |
| 5248 case kNone: | 5297 case kNone: |
| 5249 return false; | 5298 return false; |
| 5250 } | 5299 } |
| 5251 | 5300 |
| 5252 UNREACHABLE(); | 5301 UNREACHABLE(); |
| 5253 return false; | 5302 return false; |
| 5254 } | 5303 } |
| 5255 | 5304 |
| 5256 // Given instance dependent alias X.f, X.@offs, X[C], X[*] return | 5305 // Given instance dependent alias X.f, X.@offs, X[C], X[*] return |
| 5257 // wild-card dependent alias *.f, *.@offs, *[C] or *[*] respectively. | 5306 // wild-card dependent alias *.f, *.@offs, *[C] or *[*] respectively. |
| 5258 Place CopyWithoutInstance() const { | 5307 Place CopyWithoutInstance() const { |
| 5259 ASSERT(DependsOnInstance()); | 5308 ASSERT(DependsOnInstance()); |
| 5260 return Place(kind_, NULL, raw_selector_); | 5309 return Place(flags_, NULL, raw_selector_); |
| 5261 } | 5310 } |
| 5262 | 5311 |
| 5263 // Given alias X[C] or *[C] return X[*] and *[*] respectively. | 5312 // Given alias X[C] or *[C] return X[*] and *[*] respectively. |
| 5264 Place CopyWithoutIndex() const { | 5313 Place CopyWithoutIndex() const { |
| 5265 ASSERT(kind_ == kConstantIndexed); | 5314 ASSERT(kind() == kConstantIndexed); |
| 5266 return Place(kIndexed, instance_, 0); | 5315 return Place(EncodeFlags(kIndexed, kNoRepresentation, kNoSize), |
| 5316 instance_, | |
| 5317 0); | |
| 5267 } | 5318 } |
| 5268 | 5319 |
| 5320 // Given alias X[ByteOffs|S] and a larger element size S', return | |
| 5321 // alias X[RoundDown(ByteOffs, S')|S'] - this is the byte offset of a larger | |
| 5322 // typed array element that contains this typed array element. | |
| 5323 // In other words this method computes the only possible place with the given | |
| 5324 // size that can alias this place (due to alignment restrictions). | |
| 5325 // For example for X[9|kInt8] and target size kInt32 we would return | |
| 5326 // X[8|kInt32]. | |
| 5327 Place ToLargerElement(ElementSize to) const { | |
| 5328 ASSERT(kind() == kConstantIndexed); | |
| 5329 ASSERT(element_size() != kNoSize); | |
| 5330 ASSERT(element_size() < to); | |
| 5331 return Place(ElementSizeBits::update(to, flags_), | |
| 5332 instance_, | |
| 5333 RoundByteOffset(to, index_constant_)); | |
| 5334 } | |
| 5335 | |
| 5336 | |
| 5269 intptr_t id() const { return id_; } | 5337 intptr_t id() const { return id_; } |
| 5270 | 5338 |
| 5271 Kind kind() const { return kind_; } | 5339 Kind kind() const { return KindBits::decode(flags_); } |
| 5272 | 5340 |
| 5273 Representation representation() const { return representation_; } | 5341 Representation representation() const { |
| 5342 return RepresentationBits::decode(flags_); | |
| 5343 } | |
| 5274 | 5344 |
| 5275 Definition* instance() const { | 5345 Definition* instance() const { |
| 5276 ASSERT(DependsOnInstance()); | 5346 ASSERT(DependsOnInstance()); |
| 5277 return instance_; | 5347 return instance_; |
| 5278 } | 5348 } |
| 5279 | 5349 |
| 5280 void set_instance(Definition* def) { | 5350 void set_instance(Definition* def) { |
| 5281 ASSERT(DependsOnInstance()); | 5351 ASSERT(DependsOnInstance()); |
| 5282 instance_ = def->OriginalDefinition(); | 5352 instance_ = def->OriginalDefinition(); |
| 5283 } | 5353 } |
| 5284 | 5354 |
| 5285 const Field& field() const { | 5355 const Field& field() const { |
| 5286 ASSERT(kind_ == kField); | 5356 ASSERT(kind() == kField); |
| 5287 return *field_; | 5357 return *field_; |
| 5288 } | 5358 } |
| 5289 | 5359 |
| 5290 intptr_t offset_in_bytes() const { | 5360 intptr_t offset_in_bytes() const { |
| 5291 ASSERT(kind_ == kVMField); | 5361 ASSERT(kind() == kVMField); |
| 5292 return offset_in_bytes_; | 5362 return offset_in_bytes_; |
| 5293 } | 5363 } |
| 5294 | 5364 |
| 5295 Definition* index() const { | 5365 Definition* index() const { |
| 5296 ASSERT(kind_ == kIndexed); | 5366 ASSERT(kind() == kIndexed); |
| 5297 return index_; | 5367 return index_; |
| 5298 } | 5368 } |
| 5299 | 5369 |
| 5370 ElementSize element_size() const { | |
| 5371 return ElementSizeBits::decode(flags_); | |
| 5372 } | |
| 5373 | |
| 5300 intptr_t index_constant() const { | 5374 intptr_t index_constant() const { |
| 5301 ASSERT(kind_ == kConstantIndexed); | 5375 ASSERT(kind() == kConstantIndexed); |
| 5302 return index_constant_; | 5376 return index_constant_; |
| 5303 } | 5377 } |
| 5304 | 5378 |
| 5305 static const char* DefinitionName(Definition* def) { | 5379 static const char* DefinitionName(Definition* def) { |
| 5306 if (def == NULL) { | 5380 if (def == NULL) { |
| 5307 return "*"; | 5381 return "*"; |
| 5308 } else { | 5382 } else { |
| 5309 return Isolate::Current()->current_zone()->PrintToString( | 5383 return Isolate::Current()->current_zone()->PrintToString( |
| 5310 "v%" Pd, def->ssa_temp_index()); | 5384 "v%" Pd, def->ssa_temp_index()); |
| 5311 } | 5385 } |
| 5312 } | 5386 } |
| 5313 | 5387 |
| 5314 const char* ToCString() const { | 5388 const char* ToCString() const { |
| 5315 switch (kind_) { | 5389 switch (kind()) { |
| 5316 case kNone: | 5390 case kNone: |
| 5317 return "<none>"; | 5391 return "<none>"; |
| 5318 | 5392 |
| 5319 case kField: { | 5393 case kField: { |
| 5320 const char* field_name = String::Handle(field().name()).ToCString(); | 5394 const char* field_name = String::Handle(field().name()).ToCString(); |
| 5321 if (field().is_static()) { | 5395 if (field().is_static()) { |
| 5322 return Isolate::Current()->current_zone()->PrintToString( | 5396 return Isolate::Current()->current_zone()->PrintToString( |
| 5323 "<%s>", field_name); | 5397 "<%s>", field_name); |
| 5324 } else { | 5398 } else { |
| 5325 return Isolate::Current()->current_zone()->PrintToString( | 5399 return Isolate::Current()->current_zone()->PrintToString( |
| 5326 "<%s.%s>", DefinitionName(instance()), field_name); | 5400 "<%s.%s>", DefinitionName(instance()), field_name); |
| 5327 } | 5401 } |
| 5328 } | 5402 } |
| 5329 | 5403 |
| 5330 case kVMField: | 5404 case kVMField: |
| 5331 return Isolate::Current()->current_zone()->PrintToString( | 5405 return Isolate::Current()->current_zone()->PrintToString( |
| 5332 "<%s.@%" Pd ">", | 5406 "<%s.@%" Pd ">", |
| 5333 DefinitionName(instance()), | 5407 DefinitionName(instance()), |
| 5334 offset_in_bytes()); | 5408 offset_in_bytes()); |
| 5335 | 5409 |
| 5336 case kIndexed: | 5410 case kIndexed: |
| 5337 return Isolate::Current()->current_zone()->PrintToString( | 5411 return Isolate::Current()->current_zone()->PrintToString( |
| 5338 "<%s[%s]>", | 5412 "<%s[%s]>", |
| 5339 DefinitionName(instance()), | 5413 DefinitionName(instance()), |
| 5340 DefinitionName(index())); | 5414 DefinitionName(index())); |
| 5341 | 5415 |
| 5342 case kConstantIndexed: | 5416 case kConstantIndexed: |
| 5343 return Isolate::Current()->current_zone()->PrintToString( | 5417 if (element_size() == kNoSize) { |
| 5344 "<%s[%" Pd "]>", | 5418 return Isolate::Current()->current_zone()->PrintToString( |
| 5345 DefinitionName(instance()), | 5419 "<%s[%" Pd "]>", |
| 5346 index_constant()); | 5420 DefinitionName(instance()), |
| 5421 index_constant()); | |
| 5422 } else { | |
| 5423 return Isolate::Current()->current_zone()->PrintToString( | |
| 5424 "<%s[%" Pd "|%" Pd "]>", | |
| 5425 DefinitionName(instance()), | |
| 5426 index_constant(), | |
| 5427 ElementSizeMultiplier(element_size())); | |
| 5428 } | |
| 5347 } | 5429 } |
| 5348 UNREACHABLE(); | 5430 UNREACHABLE(); |
| 5349 return "<?>"; | 5431 return "<?>"; |
| 5350 } | 5432 } |
| 5351 | 5433 |
| 5352 bool IsFinalField() const { | 5434 bool IsFinalField() const { |
| 5353 return (kind() == kField) && field().is_final(); | 5435 return (kind() == kField) && field().is_final(); |
| 5354 } | 5436 } |
| 5355 | 5437 |
| 5356 intptr_t Hashcode() const { | 5438 intptr_t Hashcode() const { |
| 5357 return (kind_ * 63 + reinterpret_cast<intptr_t>(instance_)) * 31 + | 5439 return (flags_ * 63 + reinterpret_cast<intptr_t>(instance_)) * 31 + |
| 5358 representation_ * 15 + FieldHashcode(); | 5440 FieldHashcode(); |
| 5359 } | 5441 } |
| 5360 | 5442 |
| 5361 bool Equals(const Place* other) const { | 5443 bool Equals(const Place* other) const { |
| 5362 return (kind_ == other->kind_) && | 5444 return (flags_ == other->flags_) && |
| 5363 (representation_ == other->representation_) && | |
| 5364 (instance_ == other->instance_) && | 5445 (instance_ == other->instance_) && |
| 5365 SameField(other); | 5446 SameField(other); |
| 5366 } | 5447 } |
| 5367 | 5448 |
| 5368 // Create a zone allocated copy of this place and assign given id to it. | 5449 // Create a zone allocated copy of this place and assign given id to it. |
| 5369 static Place* Wrap(Isolate* isolate, const Place& place, intptr_t id); | 5450 static Place* Wrap(Isolate* isolate, const Place& place, intptr_t id); |
| 5370 | 5451 |
| 5371 static bool IsAllocation(Definition* defn) { | 5452 static bool IsAllocation(Definition* defn) { |
| 5372 return (defn != NULL) && | 5453 return (defn != NULL) && |
| 5373 (defn->IsAllocateObject() || | 5454 (defn->IsAllocateObject() || |
| 5374 defn->IsCreateArray() || | 5455 defn->IsCreateArray() || |
| 5375 defn->IsAllocateUninitializedContext() || | 5456 defn->IsAllocateUninitializedContext() || |
| 5376 (defn->IsStaticCall() && | 5457 (defn->IsStaticCall() && |
| 5377 defn->AsStaticCall()->IsRecognizedFactory())); | 5458 defn->AsStaticCall()->IsRecognizedFactory())); |
| 5378 } | 5459 } |
| 5379 | 5460 |
| 5380 private: | 5461 private: |
| 5381 Place(Kind kind, Definition* instance, intptr_t selector) | 5462 Place(uword flags, Definition* instance, intptr_t selector) |
| 5382 : kind_(kind), | 5463 : flags_(flags), |
| 5383 representation_(kNoRepresentation), | |
| 5384 instance_(instance), | 5464 instance_(instance), |
| 5385 raw_selector_(selector), | 5465 raw_selector_(selector), |
| 5386 id_(0) { | 5466 id_(0) { |
| 5387 } | 5467 } |
| 5388 | 5468 |
| 5389 bool SameField(const Place* other) const { | 5469 bool SameField(const Place* other) const { |
| 5390 return (kind_ == kField) ? (field().raw() == other->field().raw()) | 5470 return (kind() == kField) ? (field().raw() == other->field().raw()) |
| 5391 : (offset_in_bytes_ == other->offset_in_bytes_); | 5471 : (offset_in_bytes_ == other->offset_in_bytes_); |
| 5392 } | 5472 } |
| 5393 | 5473 |
| 5394 intptr_t FieldHashcode() const { | 5474 intptr_t FieldHashcode() const { |
| 5395 return (kind_ == kField) ? reinterpret_cast<intptr_t>(field().raw()) | 5475 return (kind() == kField) ? reinterpret_cast<intptr_t>(field().raw()) |
| 5396 : offset_in_bytes_; | 5476 : offset_in_bytes_; |
| 5397 } | 5477 } |
| 5398 | 5478 |
| 5399 void SetIndex(Definition* index) { | 5479 void set_representation(Representation rep) { |
| 5480 flags_ = RepresentationBits::update(rep, flags_); | |
| 5481 } | |
| 5482 | |
| 5483 void set_kind(Kind kind) { | |
| 5484 flags_ = KindBits::update(kind, flags_); | |
| 5485 } | |
| 5486 | |
| 5487 void set_element_size(ElementSize scale) { | |
| 5488 flags_ = ElementSizeBits::update(scale, flags_); | |
| 5489 } | |
| 5490 | |
| 5491 void SetIndex(Definition* index, intptr_t scale, intptr_t class_id) { | |
| 5400 ConstantInstr* index_constant = index->AsConstant(); | 5492 ConstantInstr* index_constant = index->AsConstant(); |
| 5401 if ((index_constant != NULL) && index_constant->value().IsSmi()) { | 5493 if ((index_constant != NULL) && index_constant->value().IsSmi()) { |
| 5402 kind_ = kConstantIndexed; | 5494 const intptr_t index_value = Smi::Cast(index_constant->value()).Value(); |
| 5403 index_constant_ = Smi::Cast(index_constant->value()).Value(); | 5495 const ElementSize size = ElementSizeFor(class_id); |
| 5404 } else { | 5496 const bool is_typed_data = (size != kNoSize); |
| 5405 kind_ = kIndexed; | 5497 |
| 5406 index_ = index; | 5498 // If we are writing into the typed data scale the index to |
| 5499 // get byte offset. Otherwise ignore the scale. | |
| 5500 if (!is_typed_data) { | |
| 5501 scale = 1; | |
| 5502 } | |
| 5503 | |
| 5504 // Guard against potential multiplication overflow and negative indices. | |
| 5505 if ((0 <= index_value) && (index_value < (kMaxInt32 / scale))) { | |
| 5506 const intptr_t scaled_index = index_value * scale; | |
| 5507 | |
| 5508 // Guard against unaligned byte offsets. | |
| 5509 if (!is_typed_data || | |
| 5510 Utils::IsAligned(scaled_index, ElementSizeMultiplier(size))) { | |
| 5511 set_kind(kConstantIndexed); | |
| 5512 set_element_size(size); | |
| 5513 index_constant_ = scaled_index; | |
| 5514 return; | |
| 5515 } | |
| 5516 } | |
| 5517 | |
| 5518 // Fallthrough: create generic _[*] place. | |
| 5519 } | |
| 5520 | |
| 5521 set_kind(kIndexed); | |
| 5522 index_ = index; | |
| 5523 } | |
| 5524 | |
| 5525 static uword EncodeFlags(Kind kind, Representation rep, ElementSize scale) { | |
| 5526 ASSERT((kind == kConstantIndexed) || (scale == kNoSize)); | |
| 5527 return KindBits::encode(kind) | | |
| 5528 RepresentationBits::encode(rep) | | |
| 5529 ElementSizeBits::encode(scale); | |
| 5530 } | |
| 5531 | |
| 5532 static ElementSize ElementSizeFor(intptr_t class_id) { | |
| 5533 switch (class_id) { | |
| 5534 case kArrayCid: | |
| 5535 case kImmutableArrayCid: | |
| 5536 case kOneByteStringCid: | |
| 5537 case kTwoByteStringCid: | |
| 5538 // Object arrays and strings do not allow accessing them through | |
| 5539 // different types. No need to attach scale. | |
| 5540 return kNoSize; | |
| 5541 | |
| 5542 case kTypedDataInt8ArrayCid: | |
| 5543 case kTypedDataUint8ArrayCid: | |
| 5544 case kTypedDataUint8ClampedArrayCid: | |
| 5545 case kExternalTypedDataUint8ArrayCid: | |
| 5546 case kExternalTypedDataUint8ClampedArrayCid: | |
| 5547 return kInt8; | |
| 5548 | |
| 5549 case kTypedDataInt16ArrayCid: | |
| 5550 case kTypedDataUint16ArrayCid: | |
| 5551 return kInt16; | |
| 5552 | |
| 5553 case kTypedDataInt32ArrayCid: | |
| 5554 case kTypedDataUint32ArrayCid: | |
| 5555 case kTypedDataFloat32ArrayCid: | |
| 5556 return kInt32; | |
| 5557 | |
| 5558 case kTypedDataInt64ArrayCid: | |
| 5559 case kTypedDataUint64ArrayCid: | |
| 5560 case kTypedDataFloat64ArrayCid: | |
| 5561 return kInt64; | |
| 5562 | |
| 5563 case kTypedDataInt32x4ArrayCid: | |
| 5564 case kTypedDataFloat32x4ArrayCid: | |
| 5565 case kTypedDataFloat64x2ArrayCid: | |
| 5566 return kInt128; | |
| 5567 | |
| 5568 default: | |
| 5569 UNREACHABLE(); | |
| 5570 return kNoSize; | |
| 5407 } | 5571 } |
| 5408 } | 5572 } |
| 5409 | 5573 |
| 5410 Kind kind_; | 5574 static intptr_t ElementSizeMultiplier(ElementSize size) { |
| 5411 Representation representation_; | 5575 return 1 << (static_cast<intptr_t>(size) - static_cast<intptr_t>(kInt8)); |
| 5576 } | |
| 5577 | |
| 5578 static intptr_t RoundByteOffset(ElementSize size, intptr_t offset) { | |
| 5579 return offset & ~(ElementSizeMultiplier(size) - 1); | |
| 5580 } | |
| 5581 | |
| 5582 typedef BitField<Kind, 0, 3> KindBits; | |
| 5583 typedef BitField<Representation, KindBits::kNextBit, 11> RepresentationBits; | |
| 5584 typedef BitField< | |
| 5585 ElementSize, RepresentationBits::kNextBit, 3> ElementSizeBits; | |
| 5586 | |
| 5587 uword flags_; | |
| 5412 Definition* instance_; | 5588 Definition* instance_; |
| 5413 union { | 5589 union { |
| 5414 intptr_t raw_selector_; | 5590 intptr_t raw_selector_; |
| 5415 const Field* field_; | 5591 const Field* field_; |
| 5416 intptr_t offset_in_bytes_; | 5592 intptr_t offset_in_bytes_; |
| 5417 intptr_t index_constant_; | 5593 intptr_t index_constant_; |
| 5418 Definition* index_; | 5594 Definition* index_; |
| 5419 }; | 5595 }; |
| 5420 | 5596 |
| 5421 intptr_t id_; | 5597 intptr_t id_; |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 5493 AliasedSet(Isolate* isolate, | 5669 AliasedSet(Isolate* isolate, |
| 5494 DirectChainedHashMap<PointerKeyValueTrait<Place> >* places_map, | 5670 DirectChainedHashMap<PointerKeyValueTrait<Place> >* places_map, |
| 5495 ZoneGrowableArray<Place*>* places, | 5671 ZoneGrowableArray<Place*>* places, |
| 5496 PhiPlaceMoves* phi_moves) | 5672 PhiPlaceMoves* phi_moves) |
| 5497 : isolate_(isolate), | 5673 : isolate_(isolate), |
| 5498 places_map_(places_map), | 5674 places_map_(places_map), |
| 5499 places_(*places), | 5675 places_(*places), |
| 5500 phi_moves_(phi_moves), | 5676 phi_moves_(phi_moves), |
| 5501 aliases_(5), | 5677 aliases_(5), |
| 5502 aliases_map_(), | 5678 aliases_map_(), |
| 5679 typed_data_access_sizes_(), | |
| 5503 representatives_(), | 5680 representatives_(), |
| 5504 killed_(), | 5681 killed_(), |
| 5505 aliased_by_effects_(new(isolate) BitVector(isolate, places->length())) { | 5682 aliased_by_effects_(new(isolate) BitVector(isolate, places->length())) { |
| 5506 InsertAlias(Place::CreateAnyInstanceAnyIndexAlias(isolate_, | 5683 InsertAlias(Place::CreateAnyInstanceAnyIndexAlias(isolate_, |
| 5507 kAnyInstanceAnyIndexAlias)); | 5684 kAnyInstanceAnyIndexAlias)); |
| 5508 for (intptr_t i = 0; i < places_.length(); i++) { | 5685 for (intptr_t i = 0; i < places_.length(); i++) { |
| 5509 AddRepresentative(places_[i]); | 5686 AddRepresentative(places_[i]); |
| 5510 } | 5687 } |
| 5511 ComputeKillSets(); | 5688 ComputeKillSets(); |
| 5512 } | 5689 } |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 5601 if (alias->kind() == Place::kConstantIndexed) { | 5778 if (alias->kind() == Place::kConstantIndexed) { |
| 5602 if (CanBeAliased(alias->instance())) { | 5779 if (CanBeAliased(alias->instance())) { |
| 5603 EnsureSet(&representatives_, kAnyConstantIndexedAlias)-> | 5780 EnsureSet(&representatives_, kAnyConstantIndexedAlias)-> |
| 5604 Add(place->id()); | 5781 Add(place->id()); |
| 5605 } | 5782 } |
| 5606 | 5783 |
| 5607 if (alias->instance() == NULL) { | 5784 if (alias->instance() == NULL) { |
| 5608 EnsureSet(&representatives_, kUnknownInstanceConstantIndexedAlias)-> | 5785 EnsureSet(&representatives_, kUnknownInstanceConstantIndexedAlias)-> |
| 5609 Add(place->id()); | 5786 Add(place->id()); |
| 5610 } | 5787 } |
| 5788 | |
| 5789 // Collect all element sizes used to access TypedData arrays in | |
| 5790 // the function. This is used to skip sizes without representatives | |
| 5791 // when computing kill sets. | |
| 5792 if (alias->element_size() != Place::kNoSize) { | |
| 5793 typed_data_access_sizes_.Add(alias->element_size()); | |
| 5794 } | |
| 5611 } else if ((alias->kind() == Place::kIndexed) && | 5795 } else if ((alias->kind() == Place::kIndexed) && |
| 5612 CanBeAliased(place->instance())) { | 5796 CanBeAliased(place->instance())) { |
| 5613 EnsureSet(&representatives_, kAnyAllocationIndexedAlias)-> | 5797 EnsureSet(&representatives_, kAnyAllocationIndexedAlias)-> |
| 5614 Add(place->id()); | 5798 Add(place->id()); |
| 5615 } | 5799 } |
| 5616 | 5800 |
| 5617 if (!IsIndependentFromEffects(place)) { | 5801 if (!IsIndependentFromEffects(place)) { |
| 5618 aliased_by_effects_->Add(place->id()); | 5802 aliased_by_effects_->Add(place->id()); |
| 5619 } | 5803 } |
| 5620 } | 5804 } |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 5649 } | 5833 } |
| 5650 | 5834 |
| 5651 const Place* CanonicalizeAlias(const Place& alias) { | 5835 const Place* CanonicalizeAlias(const Place& alias) { |
| 5652 const Place* canonical = aliases_map_.Lookup(&alias); | 5836 const Place* canonical = aliases_map_.Lookup(&alias); |
| 5653 if (canonical == NULL) { | 5837 if (canonical == NULL) { |
| 5654 canonical = Place::Wrap(isolate_, | 5838 canonical = Place::Wrap(isolate_, |
| 5655 alias, | 5839 alias, |
| 5656 kAnyInstanceAnyIndexAlias + aliases_.length()); | 5840 kAnyInstanceAnyIndexAlias + aliases_.length()); |
| 5657 InsertAlias(canonical); | 5841 InsertAlias(canonical); |
| 5658 } | 5842 } |
| 5843 ASSERT(aliases_map_.Lookup(&alias) == canonical); | |
| 5659 return canonical; | 5844 return canonical; |
| 5660 } | 5845 } |
| 5661 | 5846 |
| 5662 BitVector* GetRepresentativesSet(intptr_t alias) { | 5847 BitVector* GetRepresentativesSet(intptr_t alias) { |
| 5663 return (alias < representatives_.length()) ? representatives_[alias] : NULL; | 5848 return (alias < representatives_.length()) ? representatives_[alias] : NULL; |
| 5664 } | 5849 } |
| 5665 | 5850 |
| 5666 BitVector* EnsureSet(GrowableArray<BitVector*>* sets, | 5851 BitVector* EnsureSet(GrowableArray<BitVector*>* sets, |
| 5667 intptr_t alias) { | 5852 intptr_t alias) { |
| 5668 while (sets->length() <= alias) { | 5853 while (sets->length() <= alias) { |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 5718 AddAllRepresentatives(alias, kAnyAllocationIndexedAlias); | 5903 AddAllRepresentatives(alias, kAnyAllocationIndexedAlias); |
| 5719 } else if (CanBeAliased(alias->instance())) { | 5904 } else if (CanBeAliased(alias->instance())) { |
| 5720 // X[*] aliases with X[C]. | 5905 // X[*] aliases with X[C]. |
| 5721 // If X can be aliased then X[*] also aliases with *[C], *[*]. | 5906 // If X can be aliased then X[*] also aliases with *[C], *[*]. |
| 5722 CrossAlias(alias, kAnyInstanceAnyIndexAlias); | 5907 CrossAlias(alias, kAnyInstanceAnyIndexAlias); |
| 5723 AddAllRepresentatives(alias, kUnknownInstanceConstantIndexedAlias); | 5908 AddAllRepresentatives(alias, kUnknownInstanceConstantIndexedAlias); |
| 5724 } | 5909 } |
| 5725 break; | 5910 break; |
| 5726 | 5911 |
| 5727 case Place::kConstantIndexed: // Either X[C] or *[C] alias. | 5912 case Place::kConstantIndexed: // Either X[C] or *[C] alias. |
| 5913 if (alias->element_size() != Place::kNoSize) { | |
| 5914 const bool has_aliased_instance = | |
| 5915 (alias->instance() != NULL) && CanBeAliased(alias->instance()); | |
| 5916 | |
| 5917 // If this is a TypedData access then X[C|S] aliases larger elements | |
| 5918 // covering this one X[RoundDown(C, S')|S'] for all S' > S and | |
| 5919 // all smaller elements being covered by this one X[C'|S'] for | |
| 5920 // some S' < S and all C' such that C = RoundDown(C', S). | |
| 5921 // In the loop below it's enough to only propagate aliasing to | |
| 5922 // larger aliases because propagation is symmetric: smaller aliases | |
| 5923 // (if there are any) would update kill set for this alias when they | |
| 5924 // are visited. | |
| 5925 for (intptr_t i = static_cast<intptr_t>(alias->element_size()) + 1; | |
| 5926 i <= Place::kLargestElementSize; | |
| 5927 i++) { | |
| 5928 // Skip element sizes that a guaranteed to have no representatives. | |
| 5929 if (!typed_data_access_sizes_.Contains(alias->element_size())) { | |
| 5930 continue; | |
| 5931 } | |
| 5932 | |
| 5933 // X[C|S] aliases with X[RoundDown(C, S')|S'] and likewise | |
| 5934 // *[C|S] aliases with *[RoundDown(C, S')|S']. | |
| 5935 const Place larger_alias = | |
| 5936 alias->ToLargerElement(static_cast<Place::ElementSize>(i)); | |
| 5937 CrossAlias(alias, larger_alias); | |
| 5938 if (has_aliased_instance) { | |
| 5939 // If X is an aliased instance then X[C|S] aliases | |
| 5940 // with *[RoundDown(C, S')|S']. | |
| 5941 CrossAlias(alias, larger_alias.CopyWithoutInstance()); | |
| 5942 } | |
| 5943 } | |
| 5944 } | |
| 5945 | |
| 5728 if (alias->instance() == NULL) { | 5946 if (alias->instance() == NULL) { |
| 5729 // *[C] aliases with X[C], X[*], *[*]. | 5947 // *[C] aliases with X[C], X[*], *[*]. |
| 5730 AddAllRepresentatives(alias, kAnyAllocationIndexedAlias); | 5948 AddAllRepresentatives(alias, kAnyAllocationIndexedAlias); |
| 5731 CrossAlias(alias, kAnyInstanceAnyIndexAlias); | 5949 CrossAlias(alias, kAnyInstanceAnyIndexAlias); |
| 5732 } else { | 5950 } else { |
| 5733 // X[C] aliases with X[*]. | 5951 // X[C] aliases with X[*]. |
| 5734 // If X can be aliased then X[C] also aliases with *[C], *[*]. | 5952 // If X can be aliased then X[C] also aliases with *[C], *[*]. |
| 5735 CrossAlias(alias, alias->CopyWithoutIndex()); | 5953 CrossAlias(alias, alias->CopyWithoutIndex()); |
| 5736 if (CanBeAliased(alias->instance())) { | 5954 if (CanBeAliased(alias->instance())) { |
| 5737 CrossAlias(alias, alias->CopyWithoutInstance()); | 5955 CrossAlias(alias, alias->CopyWithoutInstance()); |
| (...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 5910 | 6128 |
| 5911 const ZoneGrowableArray<Place*>& places_; | 6129 const ZoneGrowableArray<Place*>& places_; |
| 5912 | 6130 |
| 5913 const PhiPlaceMoves* phi_moves_; | 6131 const PhiPlaceMoves* phi_moves_; |
| 5914 | 6132 |
| 5915 // A list of all seen aliases and a map that allows looking up canonical | 6133 // A list of all seen aliases and a map that allows looking up canonical |
| 5916 // alias object. | 6134 // alias object. |
| 5917 GrowableArray<const Place*> aliases_; | 6135 GrowableArray<const Place*> aliases_; |
| 5918 DirectChainedHashMap<PointerKeyValueTrait<const Place> > aliases_map_; | 6136 DirectChainedHashMap<PointerKeyValueTrait<const Place> > aliases_map_; |
| 5919 | 6137 |
| 6138 SmallSet<Place::ElementSize> typed_data_access_sizes_; | |
| 6139 | |
| 5920 // Maps alias id to set of ids of places representing the alias. | 6140 // Maps alias id to set of ids of places representing the alias. |
| 5921 // Place represents an alias if this alias is least generic alias for | 6141 // Place represents an alias if this alias is least generic alias for |
| 5922 // the place. | 6142 // the place. |
| 5923 // (see ToAlias for the definition of least generic alias). | 6143 // (see ToAlias for the definition of least generic alias). |
| 5924 GrowableArray<BitVector*> representatives_; | 6144 GrowableArray<BitVector*> representatives_; |
| 5925 | 6145 |
| 5926 // Maps alias id to set of ids of places aliased. | 6146 // Maps alias id to set of ids of places aliased. |
| 5927 GrowableArray<BitVector*> killed_; | 6147 GrowableArray<BitVector*> killed_; |
| 5928 | 6148 |
| 5929 // Set of ids of places that can be affected by side-effects other than | 6149 // Set of ids of places that can be affected by side-effects other than |
| (...skipping 2451 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 8381 | 8601 |
| 8382 // Insert materializations at environment uses. | 8602 // Insert materializations at environment uses. |
| 8383 for (intptr_t i = 0; i < exits_collector_.exits().length(); i++) { | 8603 for (intptr_t i = 0; i < exits_collector_.exits().length(); i++) { |
| 8384 CreateMaterializationAt( | 8604 CreateMaterializationAt( |
| 8385 exits_collector_.exits()[i], alloc, *slots); | 8605 exits_collector_.exits()[i], alloc, *slots); |
| 8386 } | 8606 } |
| 8387 } | 8607 } |
| 8388 | 8608 |
| 8389 | 8609 |
| 8390 } // namespace dart | 8610 } // namespace dart |
| OLD | NEW |