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

Side by Side Diff: runtime/vm/profiler.cc

Issue 25909002: Sampling profiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 #include <cstdio>
6
7 #include "platform/utils.h"
8
9 #include "vm/isolate.h"
10 #include "vm/json_stream.h"
11 #include "vm/native_symbol.h"
12 #include "vm/object.h"
13 #include "vm/os.h"
14 #include "vm/profiler.h"
15 #include "vm/signal_handler.h"
16
17 namespace dart {
18
19 #if defined(TARGET_OS_LINUX) || defined(TARGET_OS_MACOS) || \
20 defined(TARGET_OS_ANDROID)
21 #define PROFILER_USES_SIGNALS
22 #endif
23
24 DEFINE_FLAG(bool, profile, true, "Enable Sampling Profiler");
25
26 #if defined(PROFILER_USES_SIGNALS)
27 static void ProfileSignalAction(int signal, siginfo_t* info, void* context_);
28 #endif
29
30 class ProfilerSampleStackWalker {
31 public:
32 ProfilerSampleStackWalker(Sample* sample, uintptr_t stack_lower,
33 uintptr_t stack_upper) : sample_(sample),
34 stack_lower_(stack_lower),
35 stack_upper_(stack_upper) {
36 }
37
38 int walk(uintptr_t pc_, uintptr_t fp_) {
siva 2013/10/28 05:19:21 why underscrore for these param names?
Cutch 2013/11/04 20:36:05 Cleaned up.
39 original_pc_ = pc_;
40 original_fp_ = fp_;
41 uword* pc = reinterpret_cast<uword*>(pc_);
42 uword* fp = reinterpret_cast<uword*>(fp_);
43 int i = 0;
44 for (; i < Sample::kNumStackFrames; i++) {
45 sample_->pcs[i] = reinterpret_cast<uintptr_t>(pc);
46 if (!ValidInstructionPointer(pc) || !ValidFramePointer(fp, i)) {
47 break;
48 }
49 pc = parent_pc(fp);
50 fp = parent_fp(fp);
51 }
52 return i;
53 }
54
55 private:
56 uword* parent_pc(uword* fp) {
57 ASSERT(fp != NULL);
58 return reinterpret_cast<uword*>(*(fp+1));
59 }
60
61 uword* parent_fp(uword* fp) {
62 ASSERT(fp != NULL);
63 return reinterpret_cast<uword*>(*fp);
64 }
65
66 bool ValidInstructionPointer(uword* pc) {
67 uintptr_t cursor = reinterpret_cast<uintptr_t>(pc);
68 return cursor != 0;
69 }
70
71 bool ValidFramePointer(uword* fp, int i) {
72 uintptr_t cursor = reinterpret_cast<uintptr_t>(fp);
73 cursor += sizeof(fp);
74 bool r = cursor >= stack_lower_ && cursor <= stack_upper_;
75 return r;
76 }
77
78 Sample* sample_;
79 uintptr_t original_fp_;
80 uintptr_t original_pc_;
81 uintptr_t stack_lower_;
82 uintptr_t stack_upper_;
siva 2013/10/28 05:19:21 DISALLOW stuff
Cutch 2013/11/04 20:36:05 Done.
83 };
84
85
86 bool ProfilerManager::initialized_ = false;
87 bool ProfilerManager::shutdown_ = false;
88 Monitor* ProfilerManager::monitor_ = NULL;
89 Isolate** ProfilerManager::isolates_ = NULL;
90 intptr_t ProfilerManager::isolates_capacity_ = 0;
91 intptr_t ProfilerManager::isolates_size_ = 0;
92
93
94 void ProfilerManager::InitOnce() {
95 if (!FLAG_profile) {
96 return;
97 }
98 NativeSymbolResolver::InitOnce();
99 ASSERT(!initialized_);
100 monitor_ = new Monitor();
101 initialized_ = true;
102 ResizeIsolates(16);
103 #if defined(PROFILER_USES_SIGNALS)
104 SignalHandler::Install(ProfileSignalAction);
105 #endif
106 Thread::Start(ThreadMain, 0);
107 }
108
109
110 void ProfilerManager::Shutdown() {
111 if (!FLAG_profile) {
112 return;
113 }
114 ScopedMonitorLock lock(monitor_);
115 shutdown_ = true;
116 for (intptr_t i = 0; i < isolates_size_; i++) {
117 Isolate* isolate = isolates_[i];
siva 2013/10/28 05:19:21 ASSERT(isolate != NULL);
Cutch 2013/11/04 20:36:05 Done.
118 ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
119 FreeIsolateProfilingData(isolate);
120 }
121 isolates_size_ = 0;
122 lock.Notify();
123 NativeSymbolResolver::ShutdownOnce();
124 }
125
126
127 void ProfilerManager::SetupIsolateForProfiling(Isolate* isolate) {
128 if (!FLAG_profile) {
129 return;
130 }
131 ASSERT(isolate != NULL);
132 ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
133 SampleBuffer* sample_buffer = new SampleBuffer();
134 IsolateProfilerData* profiler_data =
135 new IsolateProfilerData(isolate, sample_buffer);
136 profiler_data->set_sample_interval(1000);
137 isolate->set_profiler_data(profiler_data);
138 }
139
140
141 void ProfilerManager::FreeIsolateProfilingData(Isolate* isolate) {
siva 2013/10/28 05:19:21 why not move the lock here ScopedMutexLock profile
Cutch 2013/11/04 20:36:05 I've added a TODO. I need to think about this agai
142 IsolateProfilerData* profiler_data = isolate->profiler_data();
143 if (profiler_data == NULL) {
144 // Already freed.
145 return;
146 }
147 isolate->set_profiler_data(NULL);
148 SampleBuffer* sample_buffer = profiler_data->sample_buffer();
149 ASSERT(sample_buffer != NULL);
150 delete sample_buffer;
151 delete profiler_data;
152 }
153
154
155 void ProfilerManager::ShutdownIsolate(Isolate* isolate) {
156 ASSERT(isolate != NULL);
157 if (!FLAG_profile) {
158 return;
159 }
160 ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
161 FreeIsolateProfilingData(isolate);
162 }
163
164
165 static void CollectSample(IsolateProfilerData* profiler_data,
166 uintptr_t pc,
167 uintptr_t fp,
168 uintptr_t stack_lower,
169 uintptr_t stack_upper) {
170 SampleBuffer* sample_buffer = profiler_data->sample_buffer();
171 Sample* sample = sample_buffer->ReserveSample();
172 ASSERT(sample != NULL);
173 sample->timestamp = OS::GetCurrentTimeMicros();
174 // TODO(johnmccutchan): Make real use of vm_tags and runtime_tags.
siva 2013/10/28 05:19:21 open an issue and use the issue number in the TODO
Cutch 2013/11/04 20:36:05 Done.
175 sample->vm_tags = Sample::kExecuting;
176 sample->runtime_tags = 0;
177 int64_t cpu_usage;
178 Thread::GetThreadCPUUsage(&cpu_usage);
179 sample->cpu_usage = profiler_data->set_and_delta_cpu_usage(cpu_usage);
180 ProfilerSampleStackWalker stackWalker(sample, stack_lower, stack_upper);
181 stackWalker.walk(pc, fp);
182 }
183
184
185 #if defined(PROFILER_USES_SIGNALS)
186 static void ProfileSignalAction(int signal, siginfo_t* info, void* context_) {
187 if (signal != SIGPROF) {
188 return;
189 }
190 ucontext_t* context = reinterpret_cast<ucontext_t*>(context_);
191 mcontext_t mcontext = context->uc_mcontext;
192 Isolate* isolate = Isolate::Current();
193 if (isolate == NULL) {
194 return;
195 }
196 {
197 ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
198 IsolateProfilerData* profiler_data = isolate->profiler_data();
199 if (profiler_data == NULL) {
200 return;
201 }
202
203 uintptr_t stack_lower = isolate->stack_limit();
204 uintptr_t stack_upper = stack_lower + isolate->GetSpecifiedStackSize();
siva 2013/10/28 05:19:21 There is an implicit assumption here that the stac
Cutch 2013/11/04 20:36:05 Yes. When would this not be true?
205 if (stack_lower == static_cast<uintptr_t>(~0)) {
206 stack_lower = isolate->saved_stack_limit();
207 stack_upper = stack_lower + isolate->GetSpecifiedStackSize();
208 }
209 if (stack_lower == static_cast<uintptr_t>(~0)) {
210 stack_lower = 0;
211 stack_upper = 0;
212 }
siva 2013/10/28 05:19:21 Not sure I understand the logic here.
Cutch 2013/11/04 20:36:05 I'm trying to get the address space bounds for the
213 uintptr_t PC = SignalHandler::GetProgramCounter(mcontext);
214 uintptr_t FP = SignalHandler::GetFramePointer(mcontext);
215 stack_lower = SignalHandler::GetStackPointer(mcontext);
216 int64_t sample_time = OS::GetCurrentTimeMicros();
217 profiler_data->SampledAt(sample_time);
218 CollectSample(profiler_data, PC, FP, stack_lower, stack_upper);
219 }
220 ProfilerManager::ScheduleIsolate(isolate);
221 }
222 #endif
223
224
225 void ProfilerManager::ScheduleIsolate(Isolate* isolate) {
226 if (!FLAG_profile) {
227 return;
228 }
229 ASSERT(initialized_);
230 ASSERT(isolate != NULL);
231 ScopedMonitorLock lock(monitor_);
232 ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
233 IsolateProfilerData* profiler_data = isolate->profiler_data();
234 if (profiler_data == NULL) {
235 return;
236 }
237 profiler_data->Scheduled(OS::GetCurrentTimeMicros(),
238 Thread::GetCurrentThreadId());
239 AddIsolate(isolate);
240 lock.Notify();
241 }
242
243
244 void ProfilerManager::DescheduleIsolate(Isolate* isolate) {
245 if (!FLAG_profile) {
246 return;
247 }
248 ASSERT(initialized_);
249 ASSERT(isolate != NULL);
250 ScopedMonitorLock lock(monitor_);
251 intptr_t i = FindIsolate(isolate);
252 if (i < 0) {
253 // Not scheduled.
254 return;
255 }
256 {
257 ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
258 IsolateProfilerData* profiler_data = isolate->profiler_data();
259 ASSERT(profiler_data != NULL);
260 profiler_data->Descheduled();
261 }
262 RemoveIsolate(i);
263 lock.Notify();
264 }
265
266
267 void PrintToJSONStream(Isolate* isolate, JSONStream* stream) {
268 ASSERT(isolate == Isolate::Current());
269 {
270 // We can't get signals here.
271 }
272 UNIMPLEMENTED();
273 }
274
275
276 void ProfilerManager::ResizeIsolates(intptr_t new_capacity) {
277 ASSERT(new_capacity > isolates_capacity_);
278 Isolate* isolate = NULL;
279 isolates_ = reinterpret_cast<Isolate**>(
280 realloc(isolates_, sizeof(isolate) * new_capacity));
281 isolates_capacity_ = new_capacity;
282 }
283
284
285 void ProfilerManager::AddIsolate(Isolate* isolate) {
286 if (isolates_size_ == isolates_capacity_) {
287 ResizeIsolates(isolates_capacity_ == 0 ? 16 : isolates_capacity_ * 2);
288 }
289 isolates_[isolates_size_] = isolate;
290 isolates_size_++;
291 }
292
293
294 intptr_t ProfilerManager::FindIsolate(Isolate* isolate) {
295 for (intptr_t i = 0; i < isolates_size_; i++) {
296 if (isolates_[i] == isolate) {
297 return i;
298 }
299 }
300 return -1;
301 }
302
303
304 void ProfilerManager::RemoveIsolate(intptr_t i) {
305 ASSERT(i < isolates_size_);
306 intptr_t last = isolates_size_ - 1;
307 if (i != last) {
308 // Swap.
309 Isolate* temp = isolates_[last];
310 isolates_[last] = isolates_[i];
311 isolates_[i] = temp;
siva 2013/10/28 05:19:21 why do they have to be swapped why not just isolat
Cutch 2013/11/04 20:36:05 Done.
312 }
313 // Mark as NULL.
314 isolates_[last] = NULL;
315 // Pop.
316 isolates_size_--;
317 }
318
319
320 #if defined(PROFILER_USES_SIGNALS)
321 int64_t ProfilerManager::SampleAndRescheduleIsolates(int64_t current_time) {
322 if (isolates_size_ == 0) {
323 return 0;
324 }
325 static const int64_t max_time = 0x7fffffffffffffffLL;
326 int64_t lowest = max_time;
327 for (intptr_t i = 0; i < isolates_size_; i++) {
328 Isolate* isolate = isolates_[i];
329 ScopedMutexLock isolate_lock(isolate->profiler_data_mutex());
330 IsolateProfilerData* profiler_data = isolate->profiler_data();
331 ASSERT(profiler_data != NULL);
332 if (profiler_data->ShouldSample(current_time)) {
333 pthread_kill(profiler_data->thread_id(), SIGPROF);
334 RemoveIsolate(i);
335 i--; // Remove moves the last element into i, this decrement cancels
336 // the increment in the for loop.
siva 2013/10/28 05:19:21 This is tricky, why not replace the for loop with
Cutch 2013/11/04 20:36:05 Done.
337 continue;
338 }
339 if (profiler_data->CanExpire()) {
340 int64_t isolate_time_left =
341 profiler_data->TimeUntilExpiration(current_time);
342 if (isolate_time_left < 0) {
343 continue;
344 }
345 if (isolate_time_left < lowest) {
346 lowest = isolate_time_left;
347 }
348 }
349 }
350 if (isolates_size_ == 0) {
351 return 0;
352 }
353 if (lowest == max_time) {
354 return 0;
355 }
356 ASSERT(lowest != max_time);
357 ASSERT(lowest > 0);
358 return lowest;
359 }
360 #else
361 int64_t ProfilerManager::SampleAndRescheduleIsolates(int64_t current_time) {
362 if (isolates_size_ == 0) {
363 return 0;
364 }
365 static const int64_t max_time = 0x7fffffffffffffffLL;
366 int64_t lowest = max_time;
367 // TODO(johnmccutchan): Implement sampling loop on Windows.
368 if (isolates_size_ == 0) {
369 return 0;
370 }
371 if (lowest == max_time) {
372 return 0;
373 }
374 ASSERT(lowest != max_time);
375 ASSERT(lowest > 0);
376 return lowest;
377 }
378 #endif
siva 2013/10/28 05:19:21 I would prefer if we moved this code to indiviual
Cutch 2013/11/04 20:36:05 Agreed and Done.
379
380
381 static const char* FindSymbolName(uintptr_t pc, bool* native_symbol) {
382 // TODO(johnmccutchan): Differentiate between symbols which can't be found
383 // and symbols which were GCed. (Heap::CodeContains).
384 ASSERT(native_symbol != NULL);
385 const char* symbol_name = "Unknown";
386 *native_symbol = false;
387 const Code& code = Code::Handle(Code::LookupCode(pc));
388 if (code.IsNull()) {
389 // Possibly a native symbol.
390 const char* native_name =
391 NativeSymbolResolver::LookupSymbolName(pc);
392 if (native_name != NULL) {
393 symbol_name = native_name;
394 *native_symbol = true;
395 }
396 } else {
397 const Function& function = Function::Handle(code.function());
398 if (!function.IsNull()) {
399 const String& name = String::Handle(function.QualifiedUserVisibleName());
400 if (!name.IsNull()) {
401 symbol_name = name.ToCString();
402 }
403 }
404 }
405 return symbol_name;
406 }
407
408
409 void ProfilerManager::WriteTracing(Isolate* isolate, const char* name,
410 Dart_Port port) {
411 ASSERT(isolate == Isolate::Current());
412 ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
413 IsolateProfilerData* profiler_data = isolate->profiler_data();
414 if (profiler_data == NULL) {
415 return;
416 }
417 SampleBuffer* sample_buffer = profiler_data->sample_buffer();
418 ASSERT(sample_buffer != NULL);
419 JSONStream stream(10 * MB);
420 intptr_t tid = reinterpret_cast<intptr_t>(sample_buffer);
421 intptr_t pid = 1;
422 {
423 JSONArray events(&stream);
424 {
425 JSONObject thread_name(&events);
426 thread_name.AddProperty("name", "thread_name");
427 thread_name.AddProperty("ph", "M");
428 thread_name.AddProperty("tid", tid);
429 thread_name.AddProperty("pid", pid);
430 {
431 JSONObject args(&thread_name, "args");
432 args.AddProperty("name", name);
433 }
434 }
435 {
436 JSONObject process_name(&events);
437 process_name.AddProperty("name", "process_name");
438 process_name.AddProperty("ph", "M");
439 process_name.AddProperty("tid", tid);
440 process_name.AddProperty("pid", pid);
441 {
442 JSONObject args(&process_name, "args");
443 args.AddProperty("name", "Dart VM");
444 }
445 }
446 uint64_t last_time = 0;
447 for (Sample* i = sample_buffer->FirstSample();
448 i != sample_buffer->LastSample();
449 i = sample_buffer->NextSample(i)) {
450 if (last_time == 0) {
451 last_time = i->timestamp;
452 }
453 intptr_t delta = i->timestamp - last_time;
454 {
455 double percentage = static_cast<double>(i->cpu_usage) /
456 static_cast<double>(delta) * 100.0;
457 if (percentage != percentage) {
458 percentage = 0.0;
459 }
460 percentage = percentage < 0.0 ? 0.0 : percentage;
461 percentage = percentage > 100.0 ? 100.0 : percentage;
462 {
463 JSONObject cpu_usage(&events);
464 cpu_usage.AddProperty("name", "CPU Usage");
465 cpu_usage.AddProperty("ph", "C");
466 cpu_usage.AddProperty("tid", tid);
467 cpu_usage.AddProperty("pid", pid);
468 cpu_usage.AddProperty("ts", static_cast<double>(last_time));
469 {
470 JSONObject args(&cpu_usage, "args");
471 args.AddProperty("CPU", percentage);
472 }
473 }
474 {
475 JSONObject cpu_usage(&events);
476 cpu_usage.AddProperty("name", "CPU Usage");
477 cpu_usage.AddProperty("ph", "C");
478 cpu_usage.AddProperty("tid", tid);
479 cpu_usage.AddProperty("pid", pid);
480 cpu_usage.AddProperty("ts", static_cast<double>(i->timestamp));
481 {
482 JSONObject args(&cpu_usage, "args");
483 args.AddProperty("CPU", percentage);
484 }
485 }
486 }
487 for (int j = 0; j < Sample::kNumStackFrames; j++) {
488 if (i->pcs[j] == 0) {
489 continue;
490 }
491 bool native_symbol = false;
492 const char* symbol_name = FindSymbolName(i->pcs[j], &native_symbol);
493 {
494 JSONObject begin(&events);
495 begin.AddProperty("ph", "B");
496 begin.AddProperty("tid", tid);
497 begin.AddProperty("pid", pid);
498 begin.AddProperty("name", symbol_name);
499 begin.AddProperty("ts", static_cast<double>(last_time));
500 }
501 if (native_symbol) {
502 NativeSymbolResolver::FreeSymbolName(symbol_name);
503 }
504 }
505 for (int j = Sample::kNumStackFrames-1; j >= 0; j--) {
506 if (i->pcs[j] == 0) {
507 continue;
508 }
509 bool native_symbol = false;
510 const char* symbol_name = FindSymbolName(i->pcs[j], &native_symbol);
511 {
512 JSONObject end(&events);
513 end.AddProperty("ph", "E");
514 end.AddProperty("tid", tid);
515 end.AddProperty("pid", pid);
516 end.AddProperty("name", symbol_name);
517 end.AddProperty("ts", static_cast<double>(i->timestamp));
518 }
519 if (native_symbol) {
520 NativeSymbolResolver::FreeSymbolName(symbol_name);
521 }
522 }
523 last_time = i->timestamp;
524 }
525 }
526 char fname[1024];
527 snprintf(fname, sizeof(fname)-1, "/tmp/isolate-%d.prof",
528 static_cast<int>(port));
529 FILE* f = fopen(fname, "wb");
530 ASSERT(f != NULL);
531 fputs(stream.ToCString(), f);
532 fclose(f);
533 }
534
535
536 void ProfilerManager::ThreadMain(uword parameters) {
537 ASSERT(initialized_);
538 ASSERT(FLAG_profile);
539 ScopedMonitorLock lock(monitor_);
540 while (!shutdown_) {
541 int64_t current_time = OS::GetCurrentTimeMicros();
542 int64_t next_sample = SampleAndRescheduleIsolates(current_time);
543 lock.WaitMicros(next_sample);
544 }
545 }
546
547
548 IsolateProfilerData::IsolateProfilerData(Isolate* isolate,
549 SampleBuffer* sample_buffer) {
550 isolate_ = isolate;
551 sample_buffer_ = sample_buffer;
552 timer_expiration_micros_ = kNoExpirationTime;
553 last_sampled_micros_ = 0;
554 thread_id_ = 0;
555 }
556
557
558 IsolateProfilerData::~IsolateProfilerData() {
559 }
560
561
562 void IsolateProfilerData::SampledAt(int64_t current_time) {
563 last_sampled_micros_ = current_time;
564 }
565
566
567 void IsolateProfilerData::Scheduled(int64_t current_time, ThreadId thread_id) {
568 timer_expiration_micros_ = current_time + sample_interval_micros_;
569 Thread::GetThreadCPUUsage(&cpu_usage_);
570 thread_id_ = thread_id;
571 }
572
573
574 void IsolateProfilerData::Descheduled() {
575 cpu_usage_ = kDescheduledCpuUsage;
576 timer_expiration_micros_ = kNoExpirationTime;
577 thread_id_ = 0;
578 Sample* sample = sample_buffer_->ReserveSample();
579 ASSERT(sample != NULL);
580 sample->timestamp = OS::GetCurrentTimeMicros();
581 sample->cpu_usage = 0;
582 sample->vm_tags = Sample::kIdle;
583 }
584
585
586 const char* Sample::kLookupSymbol = "Symbol Not Looked Up";
587 const char* Sample::kNoSymbol = "No Symbol Found";
588
589 Sample::Sample() {
590 timestamp = 0;
591 cpu_usage = 0;
592 for (int i = 0; i < kNumStackFrames; i++) {
593 pcs[i] = 0;
594 }
595 vm_tags = kIdle;
596 runtime_tags = 0;
597 }
598
599
600 SampleBuffer::SampleBuffer(intptr_t capacity) {
601 start_ = 0;
602 end_ = 0;
603 capacity_ = capacity;
604 samples_ = reinterpret_cast<Sample*>(calloc(capacity, sizeof(Sample)));
605 }
606
607
608 SampleBuffer::~SampleBuffer() {
609 if (samples_ != NULL) {
610 free(samples_);
611 samples_ = NULL;
612 }
613 }
614
615
616 Sample* SampleBuffer::ReserveSample() {
617 intptr_t index = end_;
618 end_ = WrapIncrement(end_);
619 if (end_ == start_) {
620 start_ = WrapIncrement(start_);
621 }
622 // Reset.
623 samples_[index] = Sample();
624 return &samples_[index];
625 }
626
627
628 Sample* SampleBuffer::FirstSample() const {
629 return &samples_[start_];
630 }
631
632
633 Sample* SampleBuffer::NextSample(Sample* sample) const {
634 ASSERT(sample >= &samples_[0]);
635 ASSERT(sample < &samples_[capacity_]);
636 intptr_t index = sample - samples_;
637 index = WrapIncrement(index);
638 return &samples_[index];
639 }
640
641
642 Sample* SampleBuffer::LastSample() const {
643 return &samples_[end_];
644 }
645
646
647 intptr_t SampleBuffer::WrapIncrement(intptr_t i) const {
648 return (i + 1) % capacity_;
649 }
650
651
652 } // namespace dart
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698