| 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/flow_graph_builder.h" | 9 #include "vm/flow_graph_builder.h" |
| 10 #include "vm/flow_graph_compiler.h" | 10 #include "vm/flow_graph_compiler.h" |
| (...skipping 3045 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3056 } | 3056 } |
| 3057 } | 3057 } |
| 3058 } | 3058 } |
| 3059 | 3059 |
| 3060 | 3060 |
| 3061 static bool IsLoadEliminationCandidate(Definition* def) { | 3061 static bool IsLoadEliminationCandidate(Definition* def) { |
| 3062 // Immutable loads (not affected by side effects) are handled | 3062 // Immutable loads (not affected by side effects) are handled |
| 3063 // in the DominatorBasedCSE pass. | 3063 // in the DominatorBasedCSE pass. |
| 3064 // TODO(fschneider): Extend to other load instructions. | 3064 // TODO(fschneider): Extend to other load instructions. |
| 3065 return (def->IsLoadField() && def->AffectedBySideEffect()) | 3065 return (def->IsLoadField() && def->AffectedBySideEffect()) |
| 3066 || def->IsLoadIndexed(); | 3066 || def->IsLoadIndexed() |
| 3067 || def->IsLoadStaticField() |
| 3068 || def->IsCurrentContext(); |
| 3067 } | 3069 } |
| 3068 | 3070 |
| 3069 | 3071 |
| 3070 static intptr_t ComputeLoadOffsetInWords(Definition* defn) { | 3072 // Alias represents a family of locations. It is used to capture aliasing |
| 3071 if (defn->IsLoadIndexed()) { | 3073 // between stores and loads. Store can alias another load or store if and only |
| 3072 // We are assuming that LoadField is never used to load the first word. | 3074 // if they have the same alias. |
| 3073 return 0; | 3075 class Alias : public ValueObject { |
| 3074 } | 3076 public: |
| 3075 | 3077 Alias(const Alias& other) : ValueObject(), alias_(other.alias_) { } |
| 3076 LoadFieldInstr* load_field = defn->AsLoadField(); | 3078 |
| 3077 if (load_field != NULL) { | 3079 // All indexed load/stores alias each other. |
| 3078 const intptr_t idx = load_field->offset_in_bytes() / kWordSize; | 3080 // TODO(vegorov): incorporate type of array into alias to disambiguate |
| 3079 ASSERT(idx > 0); | 3081 // different typed data and normal arrays. |
| 3080 return idx; | 3082 static Alias Indexes() { |
| 3081 } | 3083 return Alias(kIndexesAlias); |
| 3082 | 3084 } |
| 3083 UNREACHABLE(); | 3085 |
| 3084 return 0; | 3086 // Field load/stores alias each other when field offset matches. |
| 3085 } | 3087 // TODO(vegorov): use field information to disambiguate load/stores into |
| 3086 | 3088 // different fields that by accident share offset. |
| 3087 | 3089 static Alias Field(intptr_t offset_in_bytes) { |
| 3088 static bool IsInterferingStore(Instruction* instr, | 3090 const intptr_t idx = offset_in_bytes / kWordSize; |
| 3089 intptr_t* offset_in_words) { | 3091 ASSERT(idx >= kFirstFieldAlias); |
| 3090 if (instr->IsStoreIndexed()) { | 3092 return Alias(idx * 2); |
| 3091 // We are assuming that LoadField is never used to load the first word. | 3093 } |
| 3092 *offset_in_words = 0; | 3094 |
| 3093 return true; | 3095 // Static field load/stores alias each other. |
| 3094 } | 3096 // AliasedSet assigns ids to static fields during optimization phase. |
| 3095 | 3097 static Alias StaticField(intptr_t id) { |
| 3096 StoreInstanceFieldInstr* store_instance_field = instr->AsStoreInstanceField(); | 3098 ASSERT(id >= kFirstFieldAlias); |
| 3097 if (store_instance_field != NULL) { | 3099 return Alias(id * 2 + 1); |
| 3098 ASSERT(store_instance_field->field().Offset() != 0); | 3100 } |
| 3099 *offset_in_words = store_instance_field->field().Offset() / kWordSize; | 3101 |
| 3100 return true; | 3102 // Current context load/stores alias each other. |
| 3101 } | 3103 static Alias CurrentContext() { |
| 3102 | 3104 return Alias(kCurrentContextAlias); |
| 3103 StoreVMFieldInstr* store_vm_field = instr->AsStoreVMField(); | 3105 } |
| 3104 if (store_vm_field != NULL) { | 3106 |
| 3105 ASSERT(store_vm_field->offset_in_bytes() != 0); | 3107 // Operation does not alias anything. |
| 3106 *offset_in_words = store_vm_field->offset_in_bytes() / kWordSize; | 3108 static Alias None() { |
| 3107 return true; | 3109 return Alias(kNoneAlias); |
| 3108 } | 3110 } |
| 3109 | 3111 |
| 3110 return false; | 3112 bool IsNone() const { |
| 3111 } | 3113 return alias_ == kNoneAlias; |
| 3114 } |
| 3115 |
| 3116 // Convert this alias to a positive array index. |
| 3117 intptr_t ToIndex() const { |
| 3118 ASSERT(!IsNone()); |
| 3119 return alias_ - kAliasBase; |
| 3120 } |
| 3121 |
| 3122 private: |
| 3123 explicit Alias(intptr_t alias) : alias_(alias) { } |
| 3124 |
| 3125 enum { |
| 3126 kNoneAlias = -2, |
| 3127 kCurrentContextAlias = -1, |
| 3128 kIndexesAlias = 0, |
| 3129 kFirstFieldAlias = kIndexesAlias + 1, |
| 3130 kAliasBase = kCurrentContextAlias |
| 3131 }; |
| 3132 |
| 3133 const intptr_t alias_; |
| 3134 }; |
| 3135 |
| 3136 |
| 3137 // Set mapping alias to a list of loads sharing this alias. |
| 3138 class AliasedSet : public ZoneAllocated { |
| 3139 public: |
| 3140 explicit AliasedSet(intptr_t max_expr_id) |
| 3141 : max_expr_id_(max_expr_id), |
| 3142 sets_(), |
| 3143 field_ids_(), |
| 3144 max_field_id_(0) { } |
| 3145 |
| 3146 Alias ComputeAliasForLoad(Definition* defn) { |
| 3147 if (defn->IsLoadIndexed()) { |
| 3148 // We are assuming that LoadField is never used to load the first word. |
| 3149 return Alias::Indexes(); |
| 3150 } |
| 3151 |
| 3152 LoadFieldInstr* load_field = defn->AsLoadField(); |
| 3153 if (load_field != NULL) { |
| 3154 return Alias::Field(load_field->offset_in_bytes()); |
| 3155 } |
| 3156 |
| 3157 if (defn->IsCurrentContext()) { |
| 3158 return Alias::CurrentContext(); |
| 3159 } |
| 3160 |
| 3161 LoadStaticFieldInstr* load_static_field = defn->AsLoadStaticField(); |
| 3162 if (load_static_field != NULL) { |
| 3163 return Alias::StaticField(GetFieldId(load_static_field->field())); |
| 3164 } |
| 3165 |
| 3166 UNREACHABLE(); |
| 3167 return Alias::None(); |
| 3168 } |
| 3169 |
| 3170 Alias ComputeAliasForStore(Instruction* instr) { |
| 3171 if (instr->IsStoreIndexed()) { |
| 3172 return Alias::Indexes(); |
| 3173 } |
| 3174 |
| 3175 StoreInstanceFieldInstr* store_instance_field = |
| 3176 instr->AsStoreInstanceField(); |
| 3177 if (store_instance_field != NULL) { |
| 3178 return Alias::Field(store_instance_field->field().Offset()); |
| 3179 } |
| 3180 |
| 3181 StoreVMFieldInstr* store_vm_field = instr->AsStoreVMField(); |
| 3182 if (store_vm_field != NULL) { |
| 3183 return Alias::Field(store_vm_field->offset_in_bytes()); |
| 3184 } |
| 3185 |
| 3186 if (instr->IsStoreContext() || instr->IsChainContext()) { |
| 3187 return Alias::CurrentContext(); |
| 3188 } |
| 3189 |
| 3190 StoreStaticFieldInstr* store_static_field = instr->AsStoreStaticField(); |
| 3191 if (store_static_field != NULL) { |
| 3192 return Alias::StaticField(GetFieldId(store_static_field->field())); |
| 3193 } |
| 3194 |
| 3195 return Alias::None(); |
| 3196 } |
| 3197 |
| 3198 bool Contains(const Alias alias) { |
| 3199 const intptr_t idx = alias.ToIndex(); |
| 3200 return (idx < sets_.length()) && (sets_[idx] != NULL); |
| 3201 } |
| 3202 |
| 3203 BitVector* Get(const Alias alias) { |
| 3204 ASSERT(Contains(alias)); |
| 3205 return sets_[alias.ToIndex()]; |
| 3206 } |
| 3207 |
| 3208 void Add(const Alias alias, intptr_t ssa_index) { |
| 3209 const intptr_t idx = alias.ToIndex(); |
| 3210 |
| 3211 while (sets_.length() <= idx) { |
| 3212 sets_.Add(NULL); |
| 3213 } |
| 3214 |
| 3215 if (sets_[idx] == NULL) { |
| 3216 sets_[idx] = new BitVector(max_expr_id_); |
| 3217 } |
| 3218 |
| 3219 sets_[idx]->Add(ssa_index); |
| 3220 } |
| 3221 |
| 3222 intptr_t max_expr_id() const { return max_expr_id_; } |
| 3223 bool IsEmpty() const { return max_expr_id_ == 0; } |
| 3224 |
| 3225 private: |
| 3226 const intptr_t max_expr_id_; |
| 3227 |
| 3228 // Maps alias index to a set of ssa indexes corresponding to loads with the |
| 3229 // given alias. |
| 3230 GrowableArray<BitVector*> sets_; |
| 3231 |
| 3232 // Get id assigned to the given field. Assign a new id if the field is seen |
| 3233 // for the first time. |
| 3234 intptr_t GetFieldId(const Field& field) { |
| 3235 intptr_t id = field_ids_.Lookup(&field); |
| 3236 if (id == 0) { |
| 3237 id = ++max_field_id_; |
| 3238 field_ids_.Insert(FieldIdPair(&field, id)); |
| 3239 } |
| 3240 return id; |
| 3241 } |
| 3242 |
| 3243 class FieldIdPair { |
| 3244 public: |
| 3245 typedef const Field* Key; |
| 3246 typedef intptr_t Value; |
| 3247 typedef FieldIdPair Pair; |
| 3248 |
| 3249 FieldIdPair(Key key, Value value) : key_(key), value_(value) { } |
| 3250 |
| 3251 static Key KeyOf(Pair kv) { |
| 3252 return kv.key_; |
| 3253 } |
| 3254 |
| 3255 static Value ValueOf(Pair kv) { |
| 3256 return kv.value_; |
| 3257 } |
| 3258 |
| 3259 static intptr_t Hashcode(Key key) { |
| 3260 return String::Handle(key->name()).Hash(); |
| 3261 } |
| 3262 |
| 3263 static inline bool IsKeyEqual(Pair kv, Key key) { |
| 3264 return KeyOf(kv)->raw() == key->raw(); |
| 3265 } |
| 3266 |
| 3267 private: |
| 3268 Key key_; |
| 3269 Value value_; |
| 3270 }; |
| 3271 |
| 3272 // Table mapping static field to their id used during optimization pass. |
| 3273 DirectChainedHashMap<FieldIdPair> field_ids_; |
| 3274 intptr_t max_field_id_; |
| 3275 }; |
| 3112 | 3276 |
| 3113 | 3277 |
| 3114 static Definition* GetStoredValue(Instruction* instr) { | 3278 static Definition* GetStoredValue(Instruction* instr) { |
| 3115 if (instr->IsStoreIndexed()) { | 3279 if (instr->IsStoreIndexed()) { |
| 3116 return instr->AsStoreIndexed()->value()->definition(); | 3280 return instr->AsStoreIndexed()->value()->definition(); |
| 3117 } | 3281 } |
| 3118 | 3282 |
| 3119 StoreInstanceFieldInstr* store_instance_field = instr->AsStoreInstanceField(); | 3283 StoreInstanceFieldInstr* store_instance_field = instr->AsStoreInstanceField(); |
| 3120 if (store_instance_field != NULL) { | 3284 if (store_instance_field != NULL) { |
| 3121 return store_instance_field->value()->definition(); | 3285 return store_instance_field->value()->definition(); |
| 3122 } | 3286 } |
| 3123 | 3287 |
| 3124 StoreVMFieldInstr* store_vm_field = instr->AsStoreVMField(); | 3288 StoreVMFieldInstr* store_vm_field = instr->AsStoreVMField(); |
| 3125 if (store_vm_field != NULL) { | 3289 if (store_vm_field != NULL) { |
| 3126 return store_vm_field->value()->definition(); | 3290 return store_vm_field->value()->definition(); |
| 3127 } | 3291 } |
| 3128 | 3292 |
| 3293 StoreStaticFieldInstr* store_static_field = instr->AsStoreStaticField(); |
| 3294 if (store_static_field != NULL) { |
| 3295 return store_static_field->value()->definition(); |
| 3296 } |
| 3297 |
| 3298 if (instr->IsStoreContext() || instr->IsChainContext()) { |
| 3299 return instr->InputAt(0)->definition(); |
| 3300 } |
| 3301 |
| 3129 UNREACHABLE(); // Should only be called for supported store instructions. | 3302 UNREACHABLE(); // Should only be called for supported store instructions. |
| 3130 return NULL; | 3303 return NULL; |
| 3131 } | 3304 } |
| 3132 | 3305 |
| 3133 | 3306 |
| 3134 // KeyValueTrait used for numbering of loads. Allows to lookup loads | 3307 // KeyValueTrait used for numbering of loads. Allows to lookup loads |
| 3135 // corresponding to stores. | 3308 // corresponding to stores. |
| 3136 class LoadKeyValueTrait { | 3309 class LoadKeyValueTrait { |
| 3137 public: | 3310 public: |
| 3138 typedef Definition* Value; | 3311 typedef Definition* Value; |
| 3139 typedef Definition* Key; | 3312 typedef Instruction* Key; |
| 3140 typedef Definition* Pair; | 3313 typedef Definition* Pair; |
| 3141 | 3314 |
| 3142 static Key KeyOf(Pair kv) { | 3315 static Key KeyOf(Pair kv) { |
| 3143 return kv; | 3316 return kv; |
| 3144 } | 3317 } |
| 3145 | 3318 |
| 3146 static Value ValueOf(Pair kv) { | 3319 static Value ValueOf(Pair kv) { |
| 3147 return kv; | 3320 return kv; |
| 3148 } | 3321 } |
| 3149 | 3322 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 3164 object = load_field->value()->definition()->ssa_temp_index(); | 3337 object = load_field->value()->definition()->ssa_temp_index(); |
| 3165 location = load_field->offset_in_bytes(); | 3338 location = load_field->offset_in_bytes(); |
| 3166 } else if (key->IsStoreInstanceField()) { | 3339 } else if (key->IsStoreInstanceField()) { |
| 3167 StoreInstanceFieldInstr* store_field = key->AsStoreInstanceField(); | 3340 StoreInstanceFieldInstr* store_field = key->AsStoreInstanceField(); |
| 3168 object = store_field->instance()->definition()->ssa_temp_index(); | 3341 object = store_field->instance()->definition()->ssa_temp_index(); |
| 3169 location = store_field->field().Offset(); | 3342 location = store_field->field().Offset(); |
| 3170 } else if (key->IsStoreVMField()) { | 3343 } else if (key->IsStoreVMField()) { |
| 3171 StoreVMFieldInstr* store_field = key->AsStoreVMField(); | 3344 StoreVMFieldInstr* store_field = key->AsStoreVMField(); |
| 3172 object = store_field->dest()->definition()->ssa_temp_index(); | 3345 object = store_field->dest()->definition()->ssa_temp_index(); |
| 3173 location = store_field->offset_in_bytes(); | 3346 location = store_field->offset_in_bytes(); |
| 3347 } else if (key->IsLoadStaticField()) { |
| 3348 LoadStaticFieldInstr* load_static_field = key->AsLoadStaticField(); |
| 3349 object = String::Handle(load_static_field->field().name()).Hash(); |
| 3350 } else if (key->IsStoreStaticField()) { |
| 3351 StoreStaticFieldInstr* store_static_field = key->AsStoreStaticField(); |
| 3352 object = String::Handle(store_static_field->field().name()).Hash(); |
| 3353 } else { |
| 3354 ASSERT(key->IsStoreContext() || |
| 3355 key->IsCurrentContext() || |
| 3356 key->IsChainContext()); |
| 3174 } | 3357 } |
| 3175 | 3358 |
| 3176 return object * 31 + location; | 3359 return object * 31 + location; |
| 3177 } | 3360 } |
| 3178 | 3361 |
| 3179 static inline bool IsKeyEqual(Pair kv, Key key) { | 3362 static inline bool IsKeyEqual(Pair kv, Key key) { |
| 3180 if (kv->Equals(key)) return true; | 3363 if (kv->Equals(key)) return true; |
| 3181 | 3364 |
| 3182 if (kv->IsLoadIndexed()) { | 3365 if (kv->IsLoadIndexed()) { |
| 3183 if (key->IsStoreIndexed()) { | 3366 if (key->IsStoreIndexed()) { |
| 3184 LoadIndexedInstr* load_indexed = kv->AsLoadIndexed(); | 3367 LoadIndexedInstr* load_indexed = kv->AsLoadIndexed(); |
| 3185 StoreIndexedInstr* store_indexed = key->AsStoreIndexed(); | 3368 StoreIndexedInstr* store_indexed = key->AsStoreIndexed(); |
| 3186 return load_indexed->array()->Equals(store_indexed->array()) && | 3369 return load_indexed->array()->Equals(store_indexed->array()) && |
| 3187 load_indexed->index()->Equals(store_indexed->index()); | 3370 load_indexed->index()->Equals(store_indexed->index()); |
| 3188 } | 3371 } |
| 3189 return false; | 3372 return false; |
| 3190 } | 3373 } |
| 3191 | 3374 |
| 3375 if (kv->IsLoadStaticField()) { |
| 3376 if (key->IsStoreStaticField()) { |
| 3377 LoadStaticFieldInstr* load_static_field = kv->AsLoadStaticField(); |
| 3378 StoreStaticFieldInstr* store_static_field = key->AsStoreStaticField(); |
| 3379 return load_static_field->field().raw() == |
| 3380 store_static_field->field().raw(); |
| 3381 } |
| 3382 return false; |
| 3383 } |
| 3384 |
| 3385 if (kv->IsCurrentContext()) { |
| 3386 return key->IsStoreContext() || key->IsChainContext(); |
| 3387 } |
| 3388 |
| 3192 ASSERT(kv->IsLoadField()); | 3389 ASSERT(kv->IsLoadField()); |
| 3193 LoadFieldInstr* load_field = kv->AsLoadField(); | 3390 LoadFieldInstr* load_field = kv->AsLoadField(); |
| 3194 if (key->IsStoreVMField()) { | 3391 if (key->IsStoreVMField()) { |
| 3195 StoreVMFieldInstr* store_field = key->AsStoreVMField(); | 3392 StoreVMFieldInstr* store_field = key->AsStoreVMField(); |
| 3196 return load_field->value()->Equals(store_field->dest()) && | 3393 return load_field->value()->Equals(store_field->dest()) && |
| 3197 (load_field->offset_in_bytes() == store_field->offset_in_bytes()); | 3394 (load_field->offset_in_bytes() == store_field->offset_in_bytes()); |
| 3198 } else if (key->IsStoreInstanceField()) { | 3395 } else if (key->IsStoreInstanceField()) { |
| 3199 StoreInstanceFieldInstr* store_field = key->AsStoreInstanceField(); | 3396 StoreInstanceFieldInstr* store_field = key->AsStoreInstanceField(); |
| 3200 return load_field->value()->Equals(store_field->instance()) && | 3397 return load_field->value()->Equals(store_field->instance()) && |
| 3201 (load_field->offset_in_bytes() == store_field->field().Offset()); | 3398 (load_field->offset_in_bytes() == store_field->field().Offset()); |
| 3202 } | 3399 } |
| 3203 | 3400 |
| 3204 return false; | 3401 return false; |
| 3205 } | 3402 } |
| 3206 }; | 3403 }; |
| 3207 | 3404 |
| 3208 | 3405 |
| 3209 static intptr_t NumberLoadExpressions( | 3406 static AliasedSet* NumberLoadExpressions( |
| 3210 FlowGraph* graph, | 3407 FlowGraph* graph, |
| 3211 DirectChainedHashMap<LoadKeyValueTrait>* map, | 3408 DirectChainedHashMap<LoadKeyValueTrait>* map) { |
| 3212 GrowableArray<BitVector*>* kill_by_offs) { | |
| 3213 intptr_t expr_id = 0; | 3409 intptr_t expr_id = 0; |
| 3214 | 3410 |
| 3215 // Loads representing different expression ids will be collected and | 3411 // Loads representing different expression ids will be collected and |
| 3216 // used to build per offset kill sets. | 3412 // used to build per offset kill sets. |
| 3217 GrowableArray<Definition*> loads(10); | 3413 GrowableArray<Definition*> loads(10); |
| 3218 | 3414 |
| 3219 for (BlockIterator it = graph->reverse_postorder_iterator(); | 3415 for (BlockIterator it = graph->reverse_postorder_iterator(); |
| 3220 !it.Done(); | 3416 !it.Done(); |
| 3221 it.Advance()) { | 3417 it.Advance()) { |
| 3222 BlockEntryInstr* block = it.Current(); | 3418 BlockEntryInstr* block = it.Current(); |
| 3223 for (ForwardInstructionIterator instr_it(block); | 3419 for (ForwardInstructionIterator instr_it(block); |
| 3224 !instr_it.Done(); | 3420 !instr_it.Done(); |
| 3225 instr_it.Advance()) { | 3421 instr_it.Advance()) { |
| 3226 Definition* defn = instr_it.Current()->AsDefinition(); | 3422 Definition* defn = instr_it.Current()->AsDefinition(); |
| 3227 if ((defn == NULL) || !IsLoadEliminationCandidate(defn)) { | 3423 if ((defn == NULL) || !IsLoadEliminationCandidate(defn)) { |
| 3228 continue; | 3424 continue; |
| 3229 } | 3425 } |
| 3230 Definition* result = map->Lookup(defn); | 3426 Definition* result = map->Lookup(defn); |
| 3231 if (result == NULL) { | 3427 if (result == NULL) { |
| 3232 map->Insert(defn); | 3428 map->Insert(defn); |
| 3233 defn->set_expr_id(expr_id++); | 3429 defn->set_expr_id(expr_id++); |
| 3234 loads.Add(defn); | 3430 loads.Add(defn); |
| 3235 } else { | 3431 } else { |
| 3236 defn->set_expr_id(result->expr_id()); | 3432 defn->set_expr_id(result->expr_id()); |
| 3237 } | 3433 } |
| 3238 } | 3434 } |
| 3239 } | 3435 } |
| 3240 | 3436 |
| 3241 // Build per offset kill sets. Any store interferes only with loads from | 3437 // Build aliasing sets mapping aliases to loads. |
| 3242 // the same offset. | 3438 AliasedSet* aliased_set = new AliasedSet(expr_id); |
| 3243 for (intptr_t i = 0; i < loads.length(); i++) { | 3439 for (intptr_t i = 0; i < loads.length(); i++) { |
| 3244 Definition* defn = loads[i]; | 3440 Definition* defn = loads[i]; |
| 3245 | 3441 aliased_set->Add(aliased_set->ComputeAliasForLoad(defn), defn->expr_id()); |
| 3246 const intptr_t offset_in_words = ComputeLoadOffsetInWords(defn); | |
| 3247 while (kill_by_offs->length() <= offset_in_words) { | |
| 3248 kill_by_offs->Add(NULL); | |
| 3249 } | |
| 3250 if ((*kill_by_offs)[offset_in_words] == NULL) { | |
| 3251 (*kill_by_offs)[offset_in_words] = new BitVector(expr_id); | |
| 3252 } | |
| 3253 (*kill_by_offs)[offset_in_words]->Add(defn->expr_id()); | |
| 3254 } | 3442 } |
| 3255 | 3443 return aliased_set; |
| 3256 return expr_id; | |
| 3257 } | 3444 } |
| 3258 | 3445 |
| 3259 | 3446 |
| 3260 class LoadOptimizer : public ValueObject { | 3447 class LoadOptimizer : public ValueObject { |
| 3261 public: | 3448 public: |
| 3262 LoadOptimizer(FlowGraph* graph, | 3449 LoadOptimizer(FlowGraph* graph, |
| 3263 intptr_t max_expr_id, | 3450 AliasedSet* aliased_set, |
| 3264 DirectChainedHashMap<LoadKeyValueTrait>* map, | 3451 DirectChainedHashMap<LoadKeyValueTrait>* map) |
| 3265 const GrowableArray<BitVector*>& kill_by_offset) | |
| 3266 : graph_(graph), | 3452 : graph_(graph), |
| 3267 map_(map), | 3453 map_(map), |
| 3268 max_expr_id_(max_expr_id), | 3454 aliased_set_(aliased_set), |
| 3269 kill_by_offset_(kill_by_offset), | |
| 3270 in_(graph_->preorder().length()), | 3455 in_(graph_->preorder().length()), |
| 3271 out_(graph_->preorder().length()), | 3456 out_(graph_->preorder().length()), |
| 3272 gen_(graph_->preorder().length()), | 3457 gen_(graph_->preorder().length()), |
| 3273 kill_(graph_->preorder().length()), | 3458 kill_(graph_->preorder().length()), |
| 3274 exposed_values_(graph_->preorder().length()), | 3459 exposed_values_(graph_->preorder().length()), |
| 3275 out_values_(graph_->preorder().length()), | 3460 out_values_(graph_->preorder().length()), |
| 3276 phis_(5), | 3461 phis_(5), |
| 3277 worklist_(5), | 3462 worklist_(5), |
| 3278 in_worklist_(NULL) { | 3463 in_worklist_(NULL), |
| 3464 forwarded_(false) { |
| 3279 const intptr_t num_blocks = graph_->preorder().length(); | 3465 const intptr_t num_blocks = graph_->preorder().length(); |
| 3280 for (intptr_t i = 0; i < num_blocks; i++) { | 3466 for (intptr_t i = 0; i < num_blocks; i++) { |
| 3281 out_.Add(new BitVector(max_expr_id_)); | 3467 out_.Add(new BitVector(aliased_set_->max_expr_id())); |
| 3282 gen_.Add(new BitVector(max_expr_id_)); | 3468 gen_.Add(new BitVector(aliased_set_->max_expr_id())); |
| 3283 kill_.Add(new BitVector(max_expr_id_)); | 3469 kill_.Add(new BitVector(aliased_set_->max_expr_id())); |
| 3284 in_.Add(new BitVector(max_expr_id_)); | 3470 in_.Add(new BitVector(aliased_set_->max_expr_id())); |
| 3285 | 3471 |
| 3286 exposed_values_.Add(NULL); | 3472 exposed_values_.Add(NULL); |
| 3287 out_values_.Add(NULL); | 3473 out_values_.Add(NULL); |
| 3288 } | 3474 } |
| 3289 } | 3475 } |
| 3290 | 3476 |
| 3291 void Optimize() { | 3477 bool Optimize() { |
| 3292 ComputeInitialSets(); | 3478 ComputeInitialSets(); |
| 3293 ComputeOutValues(); | 3479 ComputeOutValues(); |
| 3294 ForwardLoads(); | 3480 ForwardLoads(); |
| 3295 EmitPhis(); | 3481 EmitPhis(); |
| 3482 return forwarded_; |
| 3296 } | 3483 } |
| 3297 | 3484 |
| 3298 private: | 3485 private: |
| 3299 // Compute sets of loads generated and killed by each block. | 3486 // Compute sets of loads generated and killed by each block. |
| 3300 // Additionally compute upwards exposed and generated loads for each block. | 3487 // Additionally compute upwards exposed and generated loads for each block. |
| 3301 // Exposed loads are those that can be replaced if a corresponding | 3488 // Exposed loads are those that can be replaced if a corresponding |
| 3302 // reaching load will be found. | 3489 // reaching load will be found. |
| 3303 // Loads that are locally redundant will be replaced as we go through | 3490 // Loads that are locally redundant will be replaced as we go through |
| 3304 // instructions. | 3491 // instructions. |
| 3305 void ComputeInitialSets() { | 3492 void ComputeInitialSets() { |
| 3306 for (BlockIterator block_it = graph_->reverse_postorder_iterator(); | 3493 for (BlockIterator block_it = graph_->reverse_postorder_iterator(); |
| 3307 !block_it.Done(); | 3494 !block_it.Done(); |
| 3308 block_it.Advance()) { | 3495 block_it.Advance()) { |
| 3309 BlockEntryInstr* block = block_it.Current(); | 3496 BlockEntryInstr* block = block_it.Current(); |
| 3310 const intptr_t preorder_number = block->preorder_number(); | 3497 const intptr_t preorder_number = block->preorder_number(); |
| 3311 | 3498 |
| 3312 BitVector* kill = kill_[preorder_number]; | 3499 BitVector* kill = kill_[preorder_number]; |
| 3313 BitVector* gen = gen_[preorder_number]; | 3500 BitVector* gen = gen_[preorder_number]; |
| 3314 | 3501 |
| 3315 ZoneGrowableArray<Definition*>* exposed_values = NULL; | 3502 ZoneGrowableArray<Definition*>* exposed_values = NULL; |
| 3316 ZoneGrowableArray<Definition*>* out_values = NULL; | 3503 ZoneGrowableArray<Definition*>* out_values = NULL; |
| 3317 | 3504 |
| 3318 for (ForwardInstructionIterator instr_it(block); | 3505 for (ForwardInstructionIterator instr_it(block); |
| 3319 !instr_it.Done(); | 3506 !instr_it.Done(); |
| 3320 instr_it.Advance()) { | 3507 instr_it.Advance()) { |
| 3321 Instruction* instr = instr_it.Current(); | 3508 Instruction* instr = instr_it.Current(); |
| 3322 | 3509 |
| 3323 intptr_t offset_in_words = 0; | 3510 const Alias alias = aliased_set_->ComputeAliasForStore(instr); |
| 3324 if (IsInterferingStore(instr, &offset_in_words)) { | 3511 if (!alias.IsNone()) { |
| 3325 // Interfering stores kill only loads from the same offset. | 3512 // Interfering stores kill only loads from the same offset. |
| 3326 if ((offset_in_words < kill_by_offset_.length()) && | 3513 if (aliased_set_->Contains(alias)) { |
| 3327 (kill_by_offset_[offset_in_words] != NULL)) { | 3514 BitVector* killed = aliased_set_->Get(alias); |
| 3328 kill->AddAll(kill_by_offset_[offset_in_words]); | 3515 kill->AddAll(killed); |
| 3329 // There is no need to clear out_values when clearing GEN set | 3516 // There is no need to clear out_values when clearing GEN set |
| 3330 // because only those values that are in the GEN set | 3517 // because only those values that are in the GEN set |
| 3331 // will ever be used. | 3518 // will ever be used. |
| 3332 gen->RemoveAll(kill_by_offset_[offset_in_words]); | 3519 gen->RemoveAll(killed); |
| 3333 | 3520 |
| 3334 // Only forward stores to normal arrays and float64 arrays | 3521 // Only forward stores to normal arrays and float64 arrays |
| 3335 // to loads because other array stores (intXX/uintXX/float32) | 3522 // to loads because other array stores (intXX/uintXX/float32) |
| 3336 // may implicitly convert the value stored. | 3523 // may implicitly convert the value stored. |
| 3337 StoreIndexedInstr* array_store = instr->AsStoreIndexed(); | 3524 StoreIndexedInstr* array_store = instr->AsStoreIndexed(); |
| 3338 if (array_store == NULL || | 3525 if (array_store == NULL || |
| 3339 array_store->class_id() == kArrayCid || | 3526 array_store->class_id() == kArrayCid || |
| 3340 array_store->class_id() == kTypedDataFloat64ArrayCid) { | 3527 array_store->class_id() == kTypedDataFloat64ArrayCid) { |
| 3341 Definition* load = map_->Lookup(instr->AsDefinition()); | 3528 Definition* load = map_->Lookup(instr); |
| 3342 if (load != NULL) { | 3529 if (load != NULL) { |
| 3343 // Store has a corresponding numbered load. Try forwarding | 3530 // Store has a corresponding numbered load. Try forwarding |
| 3344 // stored value to it. | 3531 // stored value to it. |
| 3345 gen->Add(load->expr_id()); | 3532 gen->Add(load->expr_id()); |
| 3346 if (out_values == NULL) out_values = CreateBlockOutValues(); | 3533 if (out_values == NULL) out_values = CreateBlockOutValues(); |
| 3347 (*out_values)[load->expr_id()] = GetStoredValue(instr); | 3534 (*out_values)[load->expr_id()] = GetStoredValue(instr); |
| 3348 } | 3535 } |
| 3349 } | 3536 } |
| 3350 } | 3537 } |
| 3351 ASSERT(instr->IsDefinition() && | 3538 ASSERT(!instr->IsDefinition() || |
| 3352 !IsLoadEliminationCandidate(instr->AsDefinition())); | 3539 !IsLoadEliminationCandidate(instr->AsDefinition())); |
| 3353 continue; | 3540 continue; |
| 3354 } | 3541 } |
| 3355 | 3542 |
| 3356 // Other instructions with side effects kill all loads. | 3543 // Other instructions with side effects kill all loads. |
| 3357 if (instr->HasSideEffect()) { | 3544 if (instr->HasSideEffect()) { |
| 3358 kill->SetAll(); | 3545 kill->SetAll(); |
| 3359 // There is no need to clear out_values when clearing GEN set | 3546 // There is no need to clear out_values when clearing GEN set |
| 3360 // because only those values that are in the GEN set | 3547 // because only those values that are in the GEN set |
| 3361 // will ever be used. | 3548 // will ever be used. |
| (...skipping 14 matching lines...) Expand all Loading... |
| 3376 Definition* replacement = (*out_values)[expr_id]; | 3563 Definition* replacement = (*out_values)[expr_id]; |
| 3377 EnsureSSATempIndex(graph_, defn, replacement); | 3564 EnsureSSATempIndex(graph_, defn, replacement); |
| 3378 if (FLAG_trace_optimization) { | 3565 if (FLAG_trace_optimization) { |
| 3379 OS::Print("Replacing load v%"Pd" with v%"Pd"\n", | 3566 OS::Print("Replacing load v%"Pd" with v%"Pd"\n", |
| 3380 defn->ssa_temp_index(), | 3567 defn->ssa_temp_index(), |
| 3381 replacement->ssa_temp_index()); | 3568 replacement->ssa_temp_index()); |
| 3382 } | 3569 } |
| 3383 | 3570 |
| 3384 defn->ReplaceUsesWith(replacement); | 3571 defn->ReplaceUsesWith(replacement); |
| 3385 instr_it.RemoveCurrentFromGraph(); | 3572 instr_it.RemoveCurrentFromGraph(); |
| 3573 forwarded_ = true; |
| 3386 continue; | 3574 continue; |
| 3387 } else if (!kill->Contains(expr_id)) { | 3575 } else if (!kill->Contains(expr_id)) { |
| 3388 // This is an exposed load: it is the first representative of a | 3576 // This is an exposed load: it is the first representative of a |
| 3389 // given expression id and it is not killed on the path from | 3577 // given expression id and it is not killed on the path from |
| 3390 // the block entry. | 3578 // the block entry. |
| 3391 if (exposed_values == NULL) { | 3579 if (exposed_values == NULL) { |
| 3392 static const intptr_t kMaxExposedValuesInitialSize = 5; | 3580 static const intptr_t kMaxExposedValuesInitialSize = 5; |
| 3393 exposed_values = new ZoneGrowableArray<Definition*>( | 3581 exposed_values = new ZoneGrowableArray<Definition*>( |
| 3394 Utils::Minimum(kMaxExposedValuesInitialSize, max_expr_id_)); | 3582 Utils::Minimum(kMaxExposedValuesInitialSize, |
| 3583 aliased_set_->max_expr_id())); |
| 3395 } | 3584 } |
| 3396 | 3585 |
| 3397 exposed_values->Add(defn); | 3586 exposed_values->Add(defn); |
| 3398 } | 3587 } |
| 3399 | 3588 |
| 3400 gen->Add(expr_id); | 3589 gen->Add(expr_id); |
| 3401 | 3590 |
| 3402 if (out_values == NULL) out_values = CreateBlockOutValues(); | 3591 if (out_values == NULL) out_values = CreateBlockOutValues(); |
| 3403 (*out_values)[expr_id] = defn; | 3592 (*out_values)[expr_id] = defn; |
| 3404 } | 3593 } |
| 3405 | 3594 |
| 3406 out_[preorder_number]->CopyFrom(gen); | 3595 out_[preorder_number]->CopyFrom(gen); |
| 3407 exposed_values_[preorder_number] = exposed_values; | 3596 exposed_values_[preorder_number] = exposed_values; |
| 3408 out_values_[preorder_number] = out_values; | 3597 out_values_[preorder_number] = out_values; |
| 3409 } | 3598 } |
| 3410 } | 3599 } |
| 3411 | 3600 |
| 3412 // Compute OUT sets and corresponding out_values mappings by propagating them | 3601 // Compute OUT sets and corresponding out_values mappings by propagating them |
| 3413 // iteratively until fix point is reached. | 3602 // iteratively until fix point is reached. |
| 3414 // No replacement is done at this point and thus any out_value[expr_id] is | 3603 // No replacement is done at this point and thus any out_value[expr_id] is |
| 3415 // changed at most once: from NULL to an actual value. | 3604 // changed at most once: from NULL to an actual value. |
| 3416 // When merging incoming loads we might need to create a phi. | 3605 // When merging incoming loads we might need to create a phi. |
| 3417 // These phis are not inserted at the graph immediately because some of them | 3606 // These phis are not inserted at the graph immediately because some of them |
| 3418 // might become redundant after load forwarding is done. | 3607 // might become redundant after load forwarding is done. |
| 3419 void ComputeOutValues() { | 3608 void ComputeOutValues() { |
| 3420 BitVector* temp = new BitVector(max_expr_id_); | 3609 BitVector* temp = new BitVector(aliased_set_->max_expr_id()); |
| 3421 | 3610 |
| 3422 bool changed = true; | 3611 bool changed = true; |
| 3423 while (changed) { | 3612 while (changed) { |
| 3424 changed = false; | 3613 changed = false; |
| 3425 | 3614 |
| 3426 for (BlockIterator block_it = graph_->reverse_postorder_iterator(); | 3615 for (BlockIterator block_it = graph_->reverse_postorder_iterator(); |
| 3427 !block_it.Done(); | 3616 !block_it.Done(); |
| 3428 block_it.Advance()) { | 3617 block_it.Advance()) { |
| 3429 BlockEntryInstr* block = block_it.Current(); | 3618 BlockEntryInstr* block = block_it.Current(); |
| 3430 | 3619 |
| (...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3584 | 3773 |
| 3585 if (FLAG_trace_optimization) { | 3774 if (FLAG_trace_optimization) { |
| 3586 OS::Print("Replacing load v%"Pd" with v%"Pd"\n", | 3775 OS::Print("Replacing load v%"Pd" with v%"Pd"\n", |
| 3587 load->ssa_temp_index(), | 3776 load->ssa_temp_index(), |
| 3588 replacement->ssa_temp_index()); | 3777 replacement->ssa_temp_index()); |
| 3589 } | 3778 } |
| 3590 | 3779 |
| 3591 load->ReplaceUsesWith(replacement); | 3780 load->ReplaceUsesWith(replacement); |
| 3592 load->RemoveFromGraph(); | 3781 load->RemoveFromGraph(); |
| 3593 load->SetReplacement(replacement); | 3782 load->SetReplacement(replacement); |
| 3783 forwarded_ = true; |
| 3594 } | 3784 } |
| 3595 } | 3785 } |
| 3596 } | 3786 } |
| 3597 } | 3787 } |
| 3598 | 3788 |
| 3599 // Check if the given phi take the same value on all code paths. | 3789 // Check if the given phi take the same value on all code paths. |
| 3600 // Eliminate it as redundant if this is the case. | 3790 // Eliminate it as redundant if this is the case. |
| 3601 // When analyzing phi operands assumes that only generated during | 3791 // When analyzing phi operands assumes that only generated during |
| 3602 // this load phase can be redundant. They can be distinguished because | 3792 // this load phase can be redundant. They can be distinguished because |
| 3603 // they are not marked alive. | 3793 // they are not marked alive. |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3661 } else { | 3851 } else { |
| 3662 for (intptr_t j = phi->InputCount() - 1; j >= 0; --j) { | 3852 for (intptr_t j = phi->InputCount() - 1; j >= 0; --j) { |
| 3663 phi->InputAt(j)->RemoveFromUseList(); | 3853 phi->InputAt(j)->RemoveFromUseList(); |
| 3664 } | 3854 } |
| 3665 } | 3855 } |
| 3666 } | 3856 } |
| 3667 } | 3857 } |
| 3668 | 3858 |
| 3669 ZoneGrowableArray<Definition*>* CreateBlockOutValues() { | 3859 ZoneGrowableArray<Definition*>* CreateBlockOutValues() { |
| 3670 ZoneGrowableArray<Definition*>* out = | 3860 ZoneGrowableArray<Definition*>* out = |
| 3671 new ZoneGrowableArray<Definition*>(max_expr_id_); | 3861 new ZoneGrowableArray<Definition*>(aliased_set_->max_expr_id()); |
| 3672 for (intptr_t i = 0; i < max_expr_id_; i++) { | 3862 for (intptr_t i = 0; i < aliased_set_->max_expr_id(); i++) { |
| 3673 out->Add(NULL); | 3863 out->Add(NULL); |
| 3674 } | 3864 } |
| 3675 return out; | 3865 return out; |
| 3676 } | 3866 } |
| 3677 | 3867 |
| 3678 FlowGraph* graph_; | 3868 FlowGraph* graph_; |
| 3679 DirectChainedHashMap<LoadKeyValueTrait>* map_; | 3869 DirectChainedHashMap<LoadKeyValueTrait>* map_; |
| 3680 const intptr_t max_expr_id_; | |
| 3681 | 3870 |
| 3682 // Mapping between field offsets in words and expression ids of loads from | 3871 // Mapping between field offsets in words and expression ids of loads from |
| 3683 // that offset. | 3872 // that offset. |
| 3684 const GrowableArray<BitVector*>& kill_by_offset_; | 3873 AliasedSet* aliased_set_; |
| 3685 | 3874 |
| 3686 // Per block sets of expression ids for loads that are: incoming (available | 3875 // Per block sets of expression ids for loads that are: incoming (available |
| 3687 // on the entry), outgoing (available on the exit), generated and killed. | 3876 // on the entry), outgoing (available on the exit), generated and killed. |
| 3688 GrowableArray<BitVector*> in_; | 3877 GrowableArray<BitVector*> in_; |
| 3689 GrowableArray<BitVector*> out_; | 3878 GrowableArray<BitVector*> out_; |
| 3690 GrowableArray<BitVector*> gen_; | 3879 GrowableArray<BitVector*> gen_; |
| 3691 GrowableArray<BitVector*> kill_; | 3880 GrowableArray<BitVector*> kill_; |
| 3692 | 3881 |
| 3693 // Per block list of upwards exposed loads. | 3882 // Per block list of upwards exposed loads. |
| 3694 GrowableArray<ZoneGrowableArray<Definition*>*> exposed_values_; | 3883 GrowableArray<ZoneGrowableArray<Definition*>*> exposed_values_; |
| 3695 | 3884 |
| 3696 // Per block mappings between expression ids and outgoing definitions that | 3885 // Per block mappings between expression ids and outgoing definitions that |
| 3697 // represent those ids. | 3886 // represent those ids. |
| 3698 GrowableArray<ZoneGrowableArray<Definition*>*> out_values_; | 3887 GrowableArray<ZoneGrowableArray<Definition*>*> out_values_; |
| 3699 | 3888 |
| 3700 // List of phis generated during ComputeOutValues and ForwardLoads. | 3889 // List of phis generated during ComputeOutValues and ForwardLoads. |
| 3701 // Some of these phis might be redundant and thus a separate pass is | 3890 // Some of these phis might be redundant and thus a separate pass is |
| 3702 // needed to emit only non-redundant ones. | 3891 // needed to emit only non-redundant ones. |
| 3703 GrowableArray<PhiInstr*> phis_; | 3892 GrowableArray<PhiInstr*> phis_; |
| 3704 | 3893 |
| 3705 // Auxiliary worklist used by redundant phi elimination. | 3894 // Auxiliary worklist used by redundant phi elimination. |
| 3706 GrowableArray<PhiInstr*> worklist_; | 3895 GrowableArray<PhiInstr*> worklist_; |
| 3707 BitVector* in_worklist_; | 3896 BitVector* in_worklist_; |
| 3708 | 3897 |
| 3898 // True if any load was eliminated. |
| 3899 bool forwarded_; |
| 3900 |
| 3709 DISALLOW_COPY_AND_ASSIGN(LoadOptimizer); | 3901 DISALLOW_COPY_AND_ASSIGN(LoadOptimizer); |
| 3710 }; | 3902 }; |
| 3711 | 3903 |
| 3712 | 3904 |
| 3713 bool DominatorBasedCSE::Optimize(FlowGraph* graph) { | 3905 bool DominatorBasedCSE::Optimize(FlowGraph* graph) { |
| 3714 bool changed = false; | 3906 bool changed = false; |
| 3715 if (FLAG_load_cse) { | 3907 if (FLAG_load_cse) { |
| 3716 GrowableArray<BitVector*> kill_by_offs(10); | 3908 GrowableArray<BitVector*> kill_by_offs(10); |
| 3717 DirectChainedHashMap<LoadKeyValueTrait> map; | 3909 DirectChainedHashMap<LoadKeyValueTrait> map; |
| 3718 const intptr_t max_expr_id = | 3910 AliasedSet* aliased_set = NumberLoadExpressions(graph, &map); |
| 3719 NumberLoadExpressions(graph, &map, &kill_by_offs); | 3911 if (!aliased_set->IsEmpty()) { |
| 3720 if (max_expr_id > 0) { | 3912 // If any loads were forwarded return true from Optimize to run load |
| 3721 LoadOptimizer load_optimizer(graph, max_expr_id, &map, kill_by_offs); | 3913 // forwarding again. This will allow to forward chains of loads. |
| 3722 load_optimizer.Optimize(); | 3914 // This is especially important for context variables as they are built |
| 3915 // as loads from loaded context. |
| 3916 // TODO(vegorov): renumber newly discovered congruences during the |
| 3917 // forwarding to forward chains without running whole pass twice. |
| 3918 LoadOptimizer load_optimizer(graph, aliased_set, &map); |
| 3919 changed = load_optimizer.Optimize() || changed; |
| 3723 } | 3920 } |
| 3724 } | 3921 } |
| 3725 | 3922 |
| 3726 DirectChainedHashMap<PointerKeyValueTrait<Instruction> > map; | 3923 DirectChainedHashMap<PointerKeyValueTrait<Instruction> > map; |
| 3727 changed = OptimizeRecursive(graph, graph->graph_entry(), &map) || changed; | 3924 changed = OptimizeRecursive(graph, graph->graph_entry(), &map) || changed; |
| 3728 | 3925 |
| 3729 return changed; | 3926 return changed; |
| 3730 } | 3927 } |
| 3731 | 3928 |
| 3732 | 3929 |
| (...skipping 1386 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 5119 if (changed) { | 5316 if (changed) { |
| 5120 // We may have changed the block order and the dominator tree. | 5317 // We may have changed the block order and the dominator tree. |
| 5121 flow_graph->DiscoverBlocks(); | 5318 flow_graph->DiscoverBlocks(); |
| 5122 GrowableArray<BitVector*> dominance_frontier; | 5319 GrowableArray<BitVector*> dominance_frontier; |
| 5123 flow_graph->ComputeDominators(&dominance_frontier); | 5320 flow_graph->ComputeDominators(&dominance_frontier); |
| 5124 } | 5321 } |
| 5125 } | 5322 } |
| 5126 | 5323 |
| 5127 | 5324 |
| 5128 } // namespace dart | 5325 } // namespace dart |
| OLD | NEW |