| 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_allocator.h" | 5 #include "vm/flow_graph_allocator.h" |
| 6 | 6 |
| 7 #include "vm/bit_vector.h" | 7 #include "vm/bit_vector.h" |
| 8 #include "vm/intermediate_language.h" | 8 #include "vm/intermediate_language.h" |
| 9 #include "vm/il_printer.h" | 9 #include "vm/il_printer.h" |
| 10 #include "vm/flow_graph.h" | 10 #include "vm/flow_graph.h" |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 78 value_representations_(flow_graph.max_virtual_register_number()), | 78 value_representations_(flow_graph.max_virtual_register_number()), |
| 79 block_order_(flow_graph.reverse_postorder()), | 79 block_order_(flow_graph.reverse_postorder()), |
| 80 postorder_(flow_graph.postorder()), | 80 postorder_(flow_graph.postorder()), |
| 81 liveness_(flow_graph), | 81 liveness_(flow_graph), |
| 82 vreg_count_(flow_graph.max_virtual_register_number()), | 82 vreg_count_(flow_graph.max_virtual_register_number()), |
| 83 live_ranges_(flow_graph.max_virtual_register_number()), | 83 live_ranges_(flow_graph.max_virtual_register_number()), |
| 84 cpu_regs_(), | 84 cpu_regs_(), |
| 85 fpu_regs_(), | 85 fpu_regs_(), |
| 86 blocked_cpu_registers_(), | 86 blocked_cpu_registers_(), |
| 87 blocked_fpu_registers_(), | 87 blocked_fpu_registers_(), |
| 88 number_of_registers_(0), |
| 89 registers_(), |
| 90 blocked_registers_(), |
| 88 cpu_spill_slot_count_(0) { | 91 cpu_spill_slot_count_(0) { |
| 89 for (intptr_t i = 0; i < vreg_count_; i++) { | 92 for (intptr_t i = 0; i < vreg_count_; i++) { |
| 90 live_ranges_.Add(NULL); | 93 live_ranges_.Add(NULL); |
| 91 } | 94 } |
| 92 for (intptr_t i = 0; i < vreg_count_; i++) { | 95 for (intptr_t i = 0; i < vreg_count_; i++) { |
| 93 value_representations_.Add(kNoRepresentation); | 96 value_representations_.Add(kNoRepresentation); |
| 94 } | 97 } |
| 95 | 98 |
| 96 // All registers are marked as "not blocked" (array initialized to false). | 99 // All registers are marked as "not blocked" (array initialized to false). |
| 97 // Mark the unavailable ones as "blocked" (true). | 100 // Mark the unavailable ones as "blocked" (true). |
| (...skipping 1756 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1854 } | 1857 } |
| 1855 } | 1858 } |
| 1856 range->set_assigned_location(parent->spill_slot()); | 1859 range->set_assigned_location(parent->spill_slot()); |
| 1857 ConvertAllUses(range); | 1860 ConvertAllUses(range); |
| 1858 } | 1861 } |
| 1859 | 1862 |
| 1860 | 1863 |
| 1861 intptr_t FlowGraphAllocator::FirstIntersectionWithAllocated( | 1864 intptr_t FlowGraphAllocator::FirstIntersectionWithAllocated( |
| 1862 intptr_t reg, LiveRange* unallocated) { | 1865 intptr_t reg, LiveRange* unallocated) { |
| 1863 intptr_t intersection = kMaxPosition; | 1866 intptr_t intersection = kMaxPosition; |
| 1864 for (intptr_t i = 0; i < registers_[reg].length(); i++) { | 1867 for (intptr_t i = 0; i < registers_[reg]->length(); i++) { |
| 1865 LiveRange* allocated = registers_[reg][i]; | 1868 LiveRange* allocated = (*registers_[reg])[i]; |
| 1866 if (allocated == NULL) continue; | 1869 if (allocated == NULL) continue; |
| 1867 | 1870 |
| 1868 UseInterval* allocated_head = | 1871 UseInterval* allocated_head = |
| 1869 allocated->finger()->first_pending_use_interval(); | 1872 allocated->finger()->first_pending_use_interval(); |
| 1870 if (allocated_head->start() >= intersection) continue; | 1873 if (allocated_head->start() >= intersection) continue; |
| 1871 | 1874 |
| 1872 const intptr_t pos = FirstIntersection( | 1875 const intptr_t pos = FirstIntersection( |
| 1873 unallocated->finger()->first_pending_use_interval(), | 1876 unallocated->finger()->first_pending_use_interval(), |
| 1874 allocated_head); | 1877 allocated_head); |
| 1875 if (pos < intersection) intersection = pos; | 1878 if (pos < intersection) intersection = pos; |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1959 unallocated); | 1962 unallocated); |
| 1960 candidate = hint.register_code(); | 1963 candidate = hint.register_code(); |
| 1961 } | 1964 } |
| 1962 | 1965 |
| 1963 TRACE_ALLOC(OS::Print("found hint %s for v%" Pd ": free until %" Pd "\n", | 1966 TRACE_ALLOC(OS::Print("found hint %s for v%" Pd ": free until %" Pd "\n", |
| 1964 hint.Name(), | 1967 hint.Name(), |
| 1965 unallocated->vreg(), | 1968 unallocated->vreg(), |
| 1966 free_until)); | 1969 free_until)); |
| 1967 } else { | 1970 } else { |
| 1968 for (intptr_t reg = 0; reg < NumberOfRegisters(); ++reg) { | 1971 for (intptr_t reg = 0; reg < NumberOfRegisters(); ++reg) { |
| 1969 if (!blocked_registers_[reg] && (registers_[reg].length() == 0)) { | 1972 if (!blocked_registers_[reg] && (registers_[reg]->length() == 0)) { |
| 1970 candidate = reg; | 1973 candidate = reg; |
| 1971 free_until = kMaxPosition; | 1974 free_until = kMaxPosition; |
| 1972 break; | 1975 break; |
| 1973 } | 1976 } |
| 1974 } | 1977 } |
| 1975 } | 1978 } |
| 1976 | 1979 |
| 1977 ASSERT(0 <= kMaxPosition); | 1980 ASSERT(0 <= kMaxPosition); |
| 1978 if (free_until != kMaxPosition) { | 1981 if (free_until != kMaxPosition) { |
| 1979 for (intptr_t reg = 0; reg < NumberOfRegisters(); ++reg) { | 1982 for (intptr_t reg = 0; reg < NumberOfRegisters(); ++reg) { |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2055 TRACE_ALLOC(MakeRegisterLocation(candidate).Print()); | 2058 TRACE_ALLOC(MakeRegisterLocation(candidate).Print()); |
| 2056 TRACE_ALLOC(OS::Print(" to v%" Pd "\n", unallocated->vreg())); | 2059 TRACE_ALLOC(OS::Print(" to v%" Pd "\n", unallocated->vreg())); |
| 2057 | 2060 |
| 2058 if (free_until != kMaxPosition) { | 2061 if (free_until != kMaxPosition) { |
| 2059 // There was an intersection. Split unallocated. | 2062 // There was an intersection. Split unallocated. |
| 2060 TRACE_ALLOC(OS::Print(" splitting at %" Pd "\n", free_until)); | 2063 TRACE_ALLOC(OS::Print(" splitting at %" Pd "\n", free_until)); |
| 2061 LiveRange* tail = unallocated->SplitAt(free_until); | 2064 LiveRange* tail = unallocated->SplitAt(free_until); |
| 2062 AddToUnallocated(tail); | 2065 AddToUnallocated(tail); |
| 2063 } | 2066 } |
| 2064 | 2067 |
| 2065 registers_[candidate].Add(unallocated); | 2068 registers_[candidate]->Add(unallocated); |
| 2066 unallocated->set_assigned_location(MakeRegisterLocation(candidate)); | 2069 unallocated->set_assigned_location(MakeRegisterLocation(candidate)); |
| 2067 | 2070 |
| 2068 return true; | 2071 return true; |
| 2069 } | 2072 } |
| 2070 | 2073 |
| 2071 | 2074 |
| 2072 bool FlowGraphAllocator::RangeHasOnlyUnconstrainedUsesInLoop(LiveRange* range, | 2075 bool FlowGraphAllocator::RangeHasOnlyUnconstrainedUsesInLoop(LiveRange* range, |
| 2073 intptr_t loop_id) { | 2076 intptr_t loop_id) { |
| 2074 if (range->vreg() >= 0) { | 2077 if (range->vreg() >= 0) { |
| 2075 LiveRange* parent = GetLiveRange(range->vreg()); | 2078 LiveRange* parent = GetLiveRange(range->vreg()); |
| 2076 return parent->HasOnlyUnconstrainedUsesInLoop(loop_id); | 2079 return parent->HasOnlyUnconstrainedUsesInLoop(loop_id); |
| 2077 } | 2080 } |
| 2078 return false; | 2081 return false; |
| 2079 } | 2082 } |
| 2080 | 2083 |
| 2081 | 2084 |
| 2082 bool FlowGraphAllocator::IsCheapToEvictRegisterInLoop(BlockInfo* loop, | 2085 bool FlowGraphAllocator::IsCheapToEvictRegisterInLoop(BlockInfo* loop, |
| 2083 intptr_t reg) { | 2086 intptr_t reg) { |
| 2084 const intptr_t loop_start = loop->entry()->start_pos(); | 2087 const intptr_t loop_start = loop->entry()->start_pos(); |
| 2085 const intptr_t loop_end = loop->last_block()->end_pos(); | 2088 const intptr_t loop_end = loop->last_block()->end_pos(); |
| 2086 | 2089 |
| 2087 for (intptr_t i = 0; i < registers_[reg].length(); i++) { | 2090 for (intptr_t i = 0; i < registers_[reg]->length(); i++) { |
| 2088 LiveRange* allocated = registers_[reg][i]; | 2091 LiveRange* allocated = (*registers_[reg])[i]; |
| 2089 | 2092 |
| 2090 UseInterval* interval = allocated->finger()->first_pending_use_interval(); | 2093 UseInterval* interval = allocated->finger()->first_pending_use_interval(); |
| 2091 if (interval->Contains(loop_start)) { | 2094 if (interval->Contains(loop_start)) { |
| 2092 if (!RangeHasOnlyUnconstrainedUsesInLoop(allocated, loop->loop_id())) { | 2095 if (!RangeHasOnlyUnconstrainedUsesInLoop(allocated, loop->loop_id())) { |
| 2093 return false; | 2096 return false; |
| 2094 } | 2097 } |
| 2095 } else if (interval->start() < loop_end) { | 2098 } else if (interval->start() < loop_end) { |
| 2096 return false; | 2099 return false; |
| 2097 } | 2100 } |
| 2098 } | 2101 } |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2175 | 2178 |
| 2176 | 2179 |
| 2177 bool FlowGraphAllocator::UpdateFreeUntil(intptr_t reg, | 2180 bool FlowGraphAllocator::UpdateFreeUntil(intptr_t reg, |
| 2178 LiveRange* unallocated, | 2181 LiveRange* unallocated, |
| 2179 intptr_t* cur_free_until, | 2182 intptr_t* cur_free_until, |
| 2180 intptr_t* cur_blocked_at) { | 2183 intptr_t* cur_blocked_at) { |
| 2181 intptr_t free_until = kMaxPosition; | 2184 intptr_t free_until = kMaxPosition; |
| 2182 intptr_t blocked_at = kMaxPosition; | 2185 intptr_t blocked_at = kMaxPosition; |
| 2183 const intptr_t start = unallocated->Start(); | 2186 const intptr_t start = unallocated->Start(); |
| 2184 | 2187 |
| 2185 for (intptr_t i = 0; i < registers_[reg].length(); i++) { | 2188 for (intptr_t i = 0; i < registers_[reg]->length(); i++) { |
| 2186 LiveRange* allocated = registers_[reg][i]; | 2189 LiveRange* allocated = (*registers_[reg])[i]; |
| 2187 | 2190 |
| 2188 UseInterval* first_pending_use_interval = | 2191 UseInterval* first_pending_use_interval = |
| 2189 allocated->finger()->first_pending_use_interval(); | 2192 allocated->finger()->first_pending_use_interval(); |
| 2190 if (first_pending_use_interval->Contains(start)) { | 2193 if (first_pending_use_interval->Contains(start)) { |
| 2191 // This is an active interval. | 2194 // This is an active interval. |
| 2192 if (allocated->vreg() < 0) { | 2195 if (allocated->vreg() < 0) { |
| 2193 // This register blocked by an interval that | 2196 // This register blocked by an interval that |
| 2194 // can't be spilled. | 2197 // can't be spilled. |
| 2195 return false; | 2198 return false; |
| 2196 } | 2199 } |
| (...skipping 30 matching lines...) Expand all Loading... |
| 2227 ASSERT(free_until > *cur_free_until); | 2230 ASSERT(free_until > *cur_free_until); |
| 2228 *cur_free_until = free_until; | 2231 *cur_free_until = free_until; |
| 2229 *cur_blocked_at = blocked_at; | 2232 *cur_blocked_at = blocked_at; |
| 2230 return true; | 2233 return true; |
| 2231 } | 2234 } |
| 2232 | 2235 |
| 2233 | 2236 |
| 2234 void FlowGraphAllocator::RemoveEvicted(intptr_t reg, intptr_t first_evicted) { | 2237 void FlowGraphAllocator::RemoveEvicted(intptr_t reg, intptr_t first_evicted) { |
| 2235 intptr_t to = first_evicted; | 2238 intptr_t to = first_evicted; |
| 2236 intptr_t from = first_evicted + 1; | 2239 intptr_t from = first_evicted + 1; |
| 2237 while (from < registers_[reg].length()) { | 2240 while (from < registers_[reg]->length()) { |
| 2238 LiveRange* allocated = registers_[reg][from++]; | 2241 LiveRange* allocated = (*registers_[reg])[from++]; |
| 2239 if (allocated != NULL) registers_[reg][to++] = allocated; | 2242 if (allocated != NULL) (*registers_[reg])[to++] = allocated; |
| 2240 } | 2243 } |
| 2241 registers_[reg].TruncateTo(to); | 2244 registers_[reg]->TruncateTo(to); |
| 2242 } | 2245 } |
| 2243 | 2246 |
| 2244 | 2247 |
| 2245 void FlowGraphAllocator::AssignNonFreeRegister(LiveRange* unallocated, | 2248 void FlowGraphAllocator::AssignNonFreeRegister(LiveRange* unallocated, |
| 2246 intptr_t reg) { | 2249 intptr_t reg) { |
| 2247 intptr_t first_evicted = -1; | 2250 intptr_t first_evicted = -1; |
| 2248 for (intptr_t i = registers_[reg].length() - 1; i >= 0; i--) { | 2251 for (intptr_t i = registers_[reg]->length() - 1; i >= 0; i--) { |
| 2249 LiveRange* allocated = registers_[reg][i]; | 2252 LiveRange* allocated = (*registers_[reg])[i]; |
| 2250 if (allocated->vreg() < 0) continue; // Can't be evicted. | 2253 if (allocated->vreg() < 0) continue; // Can't be evicted. |
| 2251 if (EvictIntersection(allocated, unallocated)) { | 2254 if (EvictIntersection(allocated, unallocated)) { |
| 2252 // If allocated was not spilled convert all pending uses. | 2255 // If allocated was not spilled convert all pending uses. |
| 2253 if (allocated->assigned_location().IsMachineRegister()) { | 2256 if (allocated->assigned_location().IsMachineRegister()) { |
| 2254 ASSERT(allocated->End() <= unallocated->Start()); | 2257 ASSERT(allocated->End() <= unallocated->Start()); |
| 2255 ConvertAllUses(allocated); | 2258 ConvertAllUses(allocated); |
| 2256 } | 2259 } |
| 2257 registers_[reg][i] = NULL; | 2260 (*registers_[reg])[i] = NULL; |
| 2258 first_evicted = i; | 2261 first_evicted = i; |
| 2259 } | 2262 } |
| 2260 } | 2263 } |
| 2261 | 2264 |
| 2262 // Remove evicted ranges from the array. | 2265 // Remove evicted ranges from the array. |
| 2263 if (first_evicted != -1) RemoveEvicted(reg, first_evicted); | 2266 if (first_evicted != -1) RemoveEvicted(reg, first_evicted); |
| 2264 | 2267 |
| 2265 registers_[reg].Add(unallocated); | 2268 registers_[reg]->Add(unallocated); |
| 2266 unallocated->set_assigned_location(MakeRegisterLocation(reg)); | 2269 unallocated->set_assigned_location(MakeRegisterLocation(reg)); |
| 2267 } | 2270 } |
| 2268 | 2271 |
| 2269 | 2272 |
| 2270 bool FlowGraphAllocator::EvictIntersection(LiveRange* allocated, | 2273 bool FlowGraphAllocator::EvictIntersection(LiveRange* allocated, |
| 2271 LiveRange* unallocated) { | 2274 LiveRange* unallocated) { |
| 2272 UseInterval* first_unallocated = | 2275 UseInterval* first_unallocated = |
| 2273 unallocated->finger()->first_pending_use_interval(); | 2276 unallocated->finger()->first_pending_use_interval(); |
| 2274 const intptr_t intersection = FirstIntersection( | 2277 const intptr_t intersection = FirstIntersection( |
| 2275 allocated->finger()->first_pending_use_interval(), | 2278 allocated->finger()->first_pending_use_interval(), |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2348 ASSERT(safepoint->locs()->can_call()); | 2351 ASSERT(safepoint->locs()->can_call()); |
| 2349 safepoint->locs()->live_registers()->Add(loc); | 2352 safepoint->locs()->live_registers()->Add(loc); |
| 2350 } | 2353 } |
| 2351 } | 2354 } |
| 2352 } | 2355 } |
| 2353 } | 2356 } |
| 2354 | 2357 |
| 2355 | 2358 |
| 2356 void FlowGraphAllocator::AdvanceActiveIntervals(const intptr_t start) { | 2359 void FlowGraphAllocator::AdvanceActiveIntervals(const intptr_t start) { |
| 2357 for (intptr_t reg = 0; reg < NumberOfRegisters(); reg++) { | 2360 for (intptr_t reg = 0; reg < NumberOfRegisters(); reg++) { |
| 2358 if (registers_[reg].is_empty()) continue; | 2361 if (registers_[reg]->is_empty()) continue; |
| 2359 | 2362 |
| 2360 intptr_t first_evicted = -1; | 2363 intptr_t first_evicted = -1; |
| 2361 for (intptr_t i = registers_[reg].length() - 1; i >= 0; i--) { | 2364 for (intptr_t i = registers_[reg]->length() - 1; i >= 0; i--) { |
| 2362 LiveRange* range = registers_[reg][i]; | 2365 LiveRange* range = (*registers_[reg])[i]; |
| 2363 if (range->finger()->Advance(start)) { | 2366 if (range->finger()->Advance(start)) { |
| 2364 ConvertAllUses(range); | 2367 ConvertAllUses(range); |
| 2365 registers_[reg][i] = NULL; | 2368 (*registers_[reg])[i] = NULL; |
| 2366 first_evicted = i; | 2369 first_evicted = i; |
| 2367 } | 2370 } |
| 2368 } | 2371 } |
| 2369 | 2372 |
| 2370 if (first_evicted != -1) RemoveEvicted(reg, first_evicted); | 2373 if (first_evicted != -1) RemoveEvicted(reg, first_evicted); |
| 2371 } | 2374 } |
| 2372 } | 2375 } |
| 2373 | 2376 |
| 2374 | 2377 |
| 2375 bool LiveRange::Contains(intptr_t pos) const { | 2378 bool LiveRange::Contains(intptr_t pos) const { |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2454 UNREACHABLE(); | 2457 UNREACHABLE(); |
| 2455 return false; | 2458 return false; |
| 2456 } | 2459 } |
| 2457 } | 2460 } |
| 2458 return true; | 2461 return true; |
| 2459 } | 2462 } |
| 2460 #endif | 2463 #endif |
| 2461 | 2464 |
| 2462 | 2465 |
| 2463 void FlowGraphAllocator::PrepareForAllocation( | 2466 void FlowGraphAllocator::PrepareForAllocation( |
| 2464 Location::Kind register_kind, | 2467 Location::Kind register_kind, |
| 2465 intptr_t number_of_registers, | 2468 intptr_t number_of_registers, |
| 2466 const GrowableArray<LiveRange*>& unallocated, | 2469 const GrowableArray<LiveRange*>& unallocated, |
| 2467 LiveRange** blocking_ranges, | 2470 LiveRange** blocking_ranges, |
| 2468 bool* blocked_registers) { | 2471 bool* blocked_registers) { |
| 2469 ASSERT(number_of_registers <= kNumberOfCpuRegisters); | |
| 2470 register_kind_ = register_kind; | 2472 register_kind_ = register_kind; |
| 2471 number_of_registers_ = number_of_registers; | 2473 number_of_registers_ = number_of_registers; |
| 2472 | 2474 |
| 2475 blocked_registers_.Clear(); |
| 2476 registers_.Clear(); |
| 2477 for (intptr_t i = 0; i < number_of_registers_; i++) { |
| 2478 blocked_registers_.Add(false); |
| 2479 registers_.Add(new ZoneGrowableArray<LiveRange*>); |
| 2480 } |
| 2473 ASSERT(unallocated_.is_empty()); | 2481 ASSERT(unallocated_.is_empty()); |
| 2474 unallocated_.AddArray(unallocated); | 2482 unallocated_.AddArray(unallocated); |
| 2475 | 2483 |
| 2476 for (intptr_t reg = 0; reg < number_of_registers; reg++) { | 2484 for (intptr_t reg = 0; reg < number_of_registers; reg++) { |
| 2477 blocked_registers_[reg] = blocked_registers[reg]; | 2485 blocked_registers_[reg] = blocked_registers[reg]; |
| 2478 ASSERT(registers_[reg].is_empty()); | 2486 ASSERT(registers_[reg]->is_empty()); |
| 2479 | 2487 |
| 2480 LiveRange* range = blocking_ranges[reg]; | 2488 LiveRange* range = blocking_ranges[reg]; |
| 2481 if (range != NULL) { | 2489 if (range != NULL) { |
| 2482 range->finger()->Initialize(range); | 2490 range->finger()->Initialize(range); |
| 2483 registers_[reg].Add(range); | 2491 registers_[reg]->Add(range); |
| 2484 } | 2492 } |
| 2485 } | 2493 } |
| 2486 } | 2494 } |
| 2487 | 2495 |
| 2488 | 2496 |
| 2489 void FlowGraphAllocator::AllocateUnallocatedRanges() { | 2497 void FlowGraphAllocator::AllocateUnallocatedRanges() { |
| 2490 #if defined(DEBUG) | 2498 #if defined(DEBUG) |
| 2491 ASSERT(UnallocatedIsSorted()); | 2499 ASSERT(UnallocatedIsSorted()); |
| 2492 #endif | 2500 #endif |
| 2493 | 2501 |
| (...skipping 284 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2778 OS::Print("-- [after ssa allocator] ir [%s] -------------\n", | 2786 OS::Print("-- [after ssa allocator] ir [%s] -------------\n", |
| 2779 function.ToFullyQualifiedCString()); | 2787 function.ToFullyQualifiedCString()); |
| 2780 FlowGraphPrinter printer(flow_graph_, true); | 2788 FlowGraphPrinter printer(flow_graph_, true); |
| 2781 printer.PrintBlocks(); | 2789 printer.PrintBlocks(); |
| 2782 OS::Print("----------------------------------------------\n"); | 2790 OS::Print("----------------------------------------------\n"); |
| 2783 } | 2791 } |
| 2784 } | 2792 } |
| 2785 | 2793 |
| 2786 | 2794 |
| 2787 } // namespace dart | 2795 } // namespace dart |
| OLD | NEW |