Prioritizing input and compositor tasks in the blink scheduler.

Review URL: https://codereview.chromium.org/439923006

git-svn-id: svn://svn.chromium.org/blink/trunk@180171 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 0945303e
...@@ -5,59 +5,93 @@ ...@@ -5,59 +5,93 @@
#include "config.h" #include "config.h"
#include "platform/scheduler/Scheduler.h" #include "platform/scheduler/Scheduler.h"
#include "platform/PlatformThreadData.h"
#include "platform/Task.h" #include "platform/Task.h"
#include "platform/ThreadTimers.h"
#include "platform/TraceEvent.h" #include "platform/TraceEvent.h"
#include "platform/TraceLocation.h"
#include "public/platform/Platform.h" #include "public/platform/Platform.h"
#include "public/platform/WebThread.h" #include "wtf/MainThread.h"
#include "wtf/ThreadingPrimitives.h"
namespace blink { namespace blink {
namespace { namespace {
class MainThreadTaskAdapter : public blink::WebThread::Task { // Can be created from any thread.
// Note if the scheduler gets shutdown, this may be run after.
class MainThreadIdleTaskAdapter : public WebThread::Task {
public: public:
explicit MainThreadTaskAdapter(const TraceLocation& location, const Scheduler::Task& task) MainThreadIdleTaskAdapter(const Scheduler::IdleTask& idleTask, double allottedTimeMs, const TraceLocation& location)
: m_location(location) : m_idleTask(idleTask)
, m_task(task) , m_allottedTimeMs(allottedTimeMs)
, m_location(location)
{ {
} }
// WebThread::Task implementation. // WebThread::Task implementation.
virtual void run() OVERRIDE virtual void run() OVERRIDE
{ {
TRACE_EVENT2("blink", "MainThreadTaskAdapter::run", TRACE_EVENT2("blink", "MainThreadIdleTaskAdapter::run",
"src_file", m_location.fileName(), "src_file", m_location.fileName(),
"src_func", m_location.functionName()); "src_func", m_location.functionName());
m_task(); m_idleTask(m_allottedTimeMs);
} }
private: private:
const TraceLocation m_location; Scheduler::IdleTask m_idleTask;
Scheduler::Task m_task; double m_allottedTimeMs;
TraceLocation m_location;
}; };
class MainThreadIdleTaskAdapter : public blink::WebThread::Task { } // namespace
// Typically only created from compositor or render threads.
// Note if the scheduler gets shutdown, this may be run after.
class Scheduler::MainThreadPendingHighPriorityTaskRunner : public WebThread::Task {
public: public:
MainThreadIdleTaskAdapter(const Scheduler::IdleTask& idleTask, double allottedTimeMs) MainThreadPendingHighPriorityTaskRunner()
: m_idleTask(idleTask)
, m_allottedTimeMs(allottedTimeMs)
{ {
ASSERT(Scheduler::shared());
} }
// WebThread::Task implementation. // WebThread::Task implementation.
virtual void run() OVERRIDE virtual void run() OVERRIDE
{ {
TRACE_EVENT1("blink", "MainThreadIdleTaskAdapter::run", "allottedTime", m_allottedTimeMs); Scheduler* scheduler = Scheduler::shared();
m_idleTask(m_allottedTimeMs); // FIXME: This check should't be necessary, tasks should not outlive blink.
ASSERT(scheduler);
if (!scheduler)
return;
scheduler->runHighPriorityTasks();
} }
private:
Scheduler::IdleTask m_idleTask;
double m_allottedTimeMs;
}; };
}
// Can be created from any thread.
// Note if the scheduler gets shutdown, this may be run after.
class Scheduler::MainThreadPendingTaskRunner : public WebThread::Task {
public:
MainThreadPendingTaskRunner(
const Scheduler::Task& task, const TraceLocation& location)
: m_task(task, location)
{
ASSERT(Scheduler::shared());
}
// WebThread::Task implementation.
virtual void run() OVERRIDE
{
Scheduler* scheduler = Scheduler::shared();
// FIXME: This check should't be necessary, tasks should not outlive blink.
ASSERT(scheduler);
if (scheduler)
Scheduler::shared()->runHighPriorityTasks();
m_task.run();
}
Scheduler::TracedTask m_task;
};
Scheduler* Scheduler::s_sharedScheduler = nullptr; Scheduler* Scheduler::s_sharedScheduler = nullptr;
...@@ -78,50 +112,89 @@ Scheduler* Scheduler::shared() ...@@ -78,50 +112,89 @@ Scheduler* Scheduler::shared()
} }
Scheduler::Scheduler() Scheduler::Scheduler()
: m_mainThread(blink::Platform::current()->currentThread()) : m_sharedTimerFunction(nullptr)
, m_sharedTimerFunction(nullptr) , m_mainThread(blink::Platform::current()->currentThread())
, m_highPriorityTaskCount(0)
{ {
} }
Scheduler::~Scheduler() Scheduler::~Scheduler()
{ {
while (hasPendingHighPriorityWork()) {
runHighPriorityTasks();
}
} }
void Scheduler::scheduleTask(const TraceLocation& location, const Task& task) void Scheduler::scheduleIdleTask(const TraceLocation& location, const IdleTask& idleTask)
{
m_mainThread->postTask(new MainThreadTaskAdapter(location, task));
}
void Scheduler::scheduleIdleTask(const IdleTask& idleTask)
{ {
// TODO: send a real allottedTime here. // TODO: send a real allottedTime here.
m_mainThread->postTask(new MainThreadIdleTaskAdapter(idleTask, 0)); m_mainThread->postTask(new MainThreadIdleTaskAdapter(idleTask, 0, location));
} }
void Scheduler::postTask(const TraceLocation& location, const Task& task) void Scheduler::postTask(const TraceLocation& location, const Task& task)
{ {
scheduleTask(location, task); m_mainThread->postTask(new MainThreadPendingTaskRunner(task, location));
} }
void Scheduler::postInputTask(const TraceLocation& location, const Task& task) void Scheduler::postInputTask(const TraceLocation& location, const Task& task)
{ {
scheduleTask(location, task); Locker<Mutex> lock(m_pendingTasksMutex);
m_pendingInputTasks.append(TracedTask(task, location));
atomicIncrement(&m_highPriorityTaskCount);
m_mainThread->postTask(new MainThreadPendingHighPriorityTaskRunner());
} }
void Scheduler::postCompositorTask(const TraceLocation& location, const Task& task) void Scheduler::postCompositorTask(const TraceLocation& location, const Task& task)
{ {
scheduleTask(location, task); Locker<Mutex> lock(m_pendingTasksMutex);
m_pendingCompositorTasks.append(TracedTask(task, location));
atomicIncrement(&m_highPriorityTaskCount);
m_mainThread->postTask(new MainThreadPendingHighPriorityTaskRunner());
} }
void Scheduler::postIdleTask(const IdleTask& idleTask) void Scheduler::postIdleTask(const TraceLocation& location, const IdleTask& idleTask)
{ {
scheduleIdleTask(idleTask); scheduleIdleTask(location, idleTask);
} }
void Scheduler::tickSharedTimer() void Scheduler::tickSharedTimer()
{ {
TRACE_EVENT0("blink", "Scheduler::tickSharedTimer"); TRACE_EVENT0("blink", "Scheduler::tickSharedTimer");
// Run any high priority tasks that are queued up, otherwise the blink timers will yield immediately.
runHighPriorityTasks();
m_sharedTimerFunction(); m_sharedTimerFunction();
// The blink timers may have just yielded, so run any high priority tasks that where queued up
// while the blink timers were executing.
runHighPriorityTasks();
}
void Scheduler::runHighPriorityTasks()
{
ASSERT(isMainThread());
TRACE_EVENT0("blink", "Scheduler::runHighPriorityTasks");
// These locks guard against another thread posting input or compositor tasks while we swap the buffers.
// One the buffers have been swapped we can safely access the returned deque without having to lock.
m_pendingTasksMutex.lock();
Deque<TracedTask>& inputTasks = m_pendingInputTasks.swapBuffers();
Deque<TracedTask>& compositorTasks = m_pendingCompositorTasks.swapBuffers();
m_pendingTasksMutex.unlock();
int highPriorityTasksExecuted = 0;
while (!inputTasks.isEmpty()) {
inputTasks.takeFirst().run();
highPriorityTasksExecuted++;
}
while (!compositorTasks.isEmpty()) {
compositorTasks.takeFirst().run();
highPriorityTasksExecuted++;
}
int highPriorityTaskCount = atomicSubtract(&m_highPriorityTaskCount, highPriorityTasksExecuted);
ASSERT_UNUSED(highPriorityTaskCount, highPriorityTaskCount >= 0);
} }
void Scheduler::sharedTimerAdapter() void Scheduler::sharedTimerAdapter()
...@@ -145,9 +218,27 @@ void Scheduler::stopSharedTimer() ...@@ -145,9 +218,27 @@ void Scheduler::stopSharedTimer()
blink::Platform::current()->stopSharedTimer(); blink::Platform::current()->stopSharedTimer();
} }
bool Scheduler::shouldYieldForHighPriorityWork() bool Scheduler::shouldYieldForHighPriorityWork() const
{
return hasPendingHighPriorityWork();
}
bool Scheduler::hasPendingHighPriorityWork() const
{
// This method is expected to be run on the main thread, but the high priority tasks will be posted by
// other threads. We could use locks here, but this function is (sometimes) called a lot by
// ThreadTimers::sharedTimerFiredInternal so we decided to use atomics + barrier loads here which
// should be cheaper.
// NOTE it's possible the barrier read is overkill here, since delayed yielding isn't a big deal.
return acquireLoad(&m_highPriorityTaskCount) != 0;
}
void Scheduler::TracedTask::run()
{ {
return false; TRACE_EVENT2("blink", "TracedTask::run",
"src_file", m_location.fileName(),
"src_func", m_location.functionName());
m_task();
} }
} // namespace blink } // namespace blink
...@@ -6,8 +6,11 @@ ...@@ -6,8 +6,11 @@
#define Scheduler_h #define Scheduler_h
#include "platform/PlatformExport.h" #include "platform/PlatformExport.h"
#include "platform/TraceLocation.h"
#include "wtf/DoubleBufferedDeque.h"
#include "wtf/Functional.h" #include "wtf/Functional.h"
#include "wtf/Noncopyable.h" #include "wtf/Noncopyable.h"
#include "wtf/ThreadingPrimitives.h"
namespace blink { namespace blink {
class WebThread; class WebThread;
...@@ -15,8 +18,6 @@ class WebThread; ...@@ -15,8 +18,6 @@ class WebThread;
namespace blink { namespace blink {
class TraceLocation;
// The scheduler is an opinionated gateway for arranging work to be run on the // The scheduler is an opinionated gateway for arranging work to be run on the
// main thread. It decides which tasks get priority over others based on a // main thread. It decides which tasks get priority over others based on a
// scheduling policy and the overall system state. // scheduling policy and the overall system state.
...@@ -36,12 +37,12 @@ public: ...@@ -36,12 +37,12 @@ public:
void postInputTask(const TraceLocation&, const Task&); void postInputTask(const TraceLocation&, const Task&);
void postCompositorTask(const TraceLocation&, const Task&); void postCompositorTask(const TraceLocation&, const Task&);
void postTask(const TraceLocation&, const Task&); // For generic (low priority) tasks. void postTask(const TraceLocation&, const Task&); // For generic (low priority) tasks.
void postIdleTask(const IdleTask&); // For non-critical tasks which may be reordered relative to other task types. void postIdleTask(const TraceLocation&, const IdleTask&); // For non-critical tasks which may be reordered relative to other task types.
// Returns true if there is high priority work pending on the main thread // Returns true if there is high priority work pending on the main thread
// and the caller should yield to let the scheduler service that work. // and the caller should yield to let the scheduler service that work.
// Can be called on the main thread. // Can be called on any thread.
bool shouldYieldForHighPriorityWork(); bool shouldYieldForHighPriorityWork() const;
// The shared timer can be used to schedule a periodic callback which may // The shared timer can be used to schedule a periodic callback which may
// get preempted by higher priority work. // get preempted by higher priority work.
...@@ -50,19 +51,49 @@ public: ...@@ -50,19 +51,49 @@ public:
void stopSharedTimer(); void stopSharedTimer();
private: private:
class MainThreadPendingTaskRunner;
class MainThreadPendingHighPriorityTaskRunner;
friend class MainThreadPendingTaskRunner;
friend class MainThreadPendingHighPriorityTaskRunner;
Scheduler(); Scheduler();
~Scheduler(); ~Scheduler();
void scheduleTask(const TraceLocation&, const Task&); void scheduleIdleTask(const TraceLocation&, const IdleTask&);
void scheduleIdleTask(const IdleTask&);
static void sharedTimerAdapter(); static void sharedTimerAdapter();
void tickSharedTimer(); void tickSharedTimer();
bool hasPendingHighPriorityWork() const;
void runHighPriorityTasks();
static Scheduler* s_sharedScheduler; static Scheduler* s_sharedScheduler;
blink::WebThread* m_mainThread;
class TracedTask {
public:
TracedTask(const Task& task, const TraceLocation& location)
: m_task(task)
, m_location(location) { }
void run();
private:
Task m_task;
TraceLocation m_location;
};
// Should only be accessed from the main thread.
void (*m_sharedTimerFunction)(); void (*m_sharedTimerFunction)();
// These members can be accessed from any thread.
WebThread* m_mainThread;
// This mutex protects calls to the pending task queues.
Mutex m_pendingTasksMutex;
DoubleBufferedDeque<TracedTask> m_pendingInputTasks;
DoubleBufferedDeque<TracedTask> m_pendingCompositorTasks;
volatile int m_highPriorityTaskCount;
}; };
} // namespace blink } // namespace blink
......
...@@ -10,9 +10,13 @@ ...@@ -10,9 +10,13 @@
#include "public/platform/Platform.h" #include "public/platform/Platform.h"
#include "public/platform/WebThread.h" #include "public/platform/WebThread.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <string>
#include <vector>
using blink::Scheduler; using blink::Scheduler;
using namespace std;
namespace { namespace {
...@@ -116,6 +120,8 @@ private: ...@@ -116,6 +120,8 @@ private:
class SchedulerTest : public testing::Test { class SchedulerTest : public testing::Test {
public: public:
SchedulerTest() SchedulerTest()
: m_reentrantCount(0)
, m_maxRecursion(4)
{ {
Scheduler::initializeOnMainThread(); Scheduler::initializeOnMainThread();
m_scheduler = Scheduler::shared(); m_scheduler = Scheduler::shared();
...@@ -131,9 +137,45 @@ public: ...@@ -131,9 +137,45 @@ public:
m_platformSupport.runPendingTasks(); m_platformSupport.runPendingTasks();
} }
void appendToVector(string value)
{
m_order.push_back(value);
}
void appendToVectorReentrantTask()
{
m_reentrantOrder.push_back(m_reentrantCount++);
if (m_reentrantCount > m_maxRecursion)
return;
Scheduler::shared()->postTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantTask, this));
}
void appendToVectorReentrantInputTask()
{
m_reentrantOrder.push_back(m_reentrantCount++);
if (m_reentrantCount > m_maxRecursion)
return;
m_scheduler->postInputTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantInputTask, this));
}
void appendToVectorReentrantCompositorTask()
{
m_reentrantOrder.push_back(m_reentrantCount++);
if (m_reentrantCount > m_maxRecursion)
return;
m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantCompositorTask, this));
}
protected: protected:
SchedulerTestingPlatformSupport m_platformSupport; SchedulerTestingPlatformSupport m_platformSupport;
Scheduler* m_scheduler; Scheduler* m_scheduler;
std::vector<string> m_order;
std::vector<int> m_reentrantOrder;
int m_reentrantCount;
int m_maxRecursion;
}; };
void orderedTestTask(int value, int* result) void orderedTestTask(int value, int* result)
...@@ -154,10 +196,10 @@ void idleTestTask(int value, int* result, double allottedTime) ...@@ -154,10 +196,10 @@ void idleTestTask(int value, int* result, double allottedTime)
TEST_F(SchedulerTest, TestPostTask) TEST_F(SchedulerTest, TestPostTask)
{ {
int result = 0; int result = 0;
m_scheduler->postTask(FROM_HERE, bind(&orderedTestTask, 1, &result)); m_scheduler->postTask(FROM_HERE, WTF::bind(&orderedTestTask, 1, &result));
m_scheduler->postTask(FROM_HERE, bind(&orderedTestTask, 2, &result)); m_scheduler->postTask(FROM_HERE, WTF::bind(&orderedTestTask, 2, &result));
m_scheduler->postTask(FROM_HERE, bind(&orderedTestTask, 3, &result)); m_scheduler->postTask(FROM_HERE, WTF::bind(&orderedTestTask, 3, &result));
m_scheduler->postTask(FROM_HERE, bind(&orderedTestTask, 4, &result)); m_scheduler->postTask(FROM_HERE, WTF::bind(&orderedTestTask, 4, &result));
runPendingTasks(); runPendingTasks();
EXPECT_EQ(0x1234, result); EXPECT_EQ(0x1234, result);
} }
...@@ -165,10 +207,10 @@ TEST_F(SchedulerTest, TestPostTask) ...@@ -165,10 +207,10 @@ TEST_F(SchedulerTest, TestPostTask)
TEST_F(SchedulerTest, TestPostMixedTaskTypes) TEST_F(SchedulerTest, TestPostMixedTaskTypes)
{ {
int result = 0; int result = 0;
m_scheduler->postTask(FROM_HERE, bind(&unorderedTestTask, 1, &result)); m_scheduler->postTask(FROM_HERE, WTF::bind(&unorderedTestTask, 1, &result));
m_scheduler->postInputTask(FROM_HERE, bind(&unorderedTestTask, 2, &result)); m_scheduler->postInputTask(FROM_HERE, WTF::bind(&unorderedTestTask, 2, &result));
m_scheduler->postCompositorTask(FROM_HERE, bind(&unorderedTestTask, 4, &result)); m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&unorderedTestTask, 4, &result));
m_scheduler->postTask(FROM_HERE, bind(&unorderedTestTask, 8, &result)); m_scheduler->postTask(FROM_HERE, WTF::bind(&unorderedTestTask, 8, &result));
runPendingTasks(); runPendingTasks();
EXPECT_EQ(15, result); EXPECT_EQ(15, result);
} }
...@@ -201,12 +243,51 @@ TEST_F(SchedulerTest, TestIdleTask) ...@@ -201,12 +243,51 @@ TEST_F(SchedulerTest, TestIdleTask)
{ {
// TODO: Check task allottedTime when implemented in the scheduler. // TODO: Check task allottedTime when implemented in the scheduler.
int result = 0; int result = 0;
m_scheduler->postIdleTask(bind<double>(&idleTestTask, 1, &result)); m_scheduler->postIdleTask(FROM_HERE, WTF::bind<double>(&idleTestTask, 1, &result));
m_scheduler->postIdleTask(bind<double>(&idleTestTask, 1, &result)); m_scheduler->postIdleTask(FROM_HERE, WTF::bind<double>(&idleTestTask, 1, &result));
m_scheduler->postIdleTask(bind<double>(&idleTestTask, 1, &result)); m_scheduler->postIdleTask(FROM_HERE, WTF::bind<double>(&idleTestTask, 1, &result));
m_scheduler->postIdleTask(bind<double>(&idleTestTask, 1, &result)); m_scheduler->postIdleTask(FROM_HERE, WTF::bind<double>(&idleTestTask, 1, &result));
runPendingTasks(); runPendingTasks();
EXPECT_EQ(4, result); EXPECT_EQ(4, result);
} }
TEST_F(SchedulerTest, TestTaskPrioritization)
{
m_scheduler->postTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("L1")));
m_scheduler->postTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("L2")));
m_scheduler->postInputTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("I1")));
m_scheduler->postInputTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("I2")));
m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("C1")));
m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("C2")));
runPendingTasks();
EXPECT_THAT(m_order, testing::ElementsAre(
string("I1"), string("I2"), string("C1"), string("C2"), string("L1"), string("L2")));
}
TEST_F(SchedulerTest, TestRentrantTask)
{
m_scheduler->postTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantTask, this));
runPendingTasks();
EXPECT_THAT(m_reentrantOrder, testing::ElementsAre(0, 1, 2, 3, 4));
}
TEST_F(SchedulerTest, TestRentrantInputTaskDuringShutdown)
{
m_scheduler->postInputTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantInputTask, this));
Scheduler::shutdown();
EXPECT_THAT(m_reentrantOrder, testing::ElementsAre(0, 1, 2, 3, 4));
}
TEST_F(SchedulerTest, TestRentrantCompositorTaskDuringShutdown)
{
m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantCompositorTask, this));
Scheduler::shutdown();
EXPECT_THAT(m_reentrantOrder, testing::ElementsAre(0, 1, 2, 3, 4));
}
} // namespace } // namespace
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DoubleBufferedDeque_h
#define DoubleBufferedDeque_h
#include "wtf/Deque.h"
#include "wtf/Noncopyable.h"
namespace WTF {
// A helper class for managing double buffered deques, typically where the client locks when appending or swapping.
template <typename T> class DoubleBufferedDeque {
WTF_MAKE_NONCOPYABLE(DoubleBufferedDeque);
public:
DoubleBufferedDeque()
: m_activeIndex(0) { }
void append(const T& value)
{
m_queue[m_activeIndex].append(value);
}
bool isEmpty() const
{
return m_queue[m_activeIndex].isEmpty();
}
Deque<T>& swapBuffers()
{
int oldIndex = m_activeIndex;
m_activeIndex ^= 1;
ASSERT(m_queue[m_activeIndex].isEmpty());
return m_queue[oldIndex];
}
private:
Deque<T> m_queue[2];
int m_activeIndex;
};
} // namespace WTF
using WTF::DoubleBufferedDeque;
#endif // DoubleBufferedDeque_h
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "config.h"
#include "wtf/DoubleBufferedDeque.h"
#include <gtest/gtest.h>
namespace {
typedef testing::Test DoubleBufferedDequeTest;
TEST(DoubleBufferedDequeTest, TestIsEmpty)
{
DoubleBufferedDeque<int> queue;
EXPECT_TRUE(queue.isEmpty());
queue.append(1);
EXPECT_FALSE(queue.isEmpty());
}
TEST(DoubleBufferedDequeTest, TestIsEmptyAfterSwapBuffers)
{
DoubleBufferedDeque<int> queue;
queue.append(1);
queue.swapBuffers();
EXPECT_TRUE(queue.isEmpty());
}
TEST(DoubleBufferedDequeTest, TestDoubleBuffering)
{
DoubleBufferedDeque<int> queue;
queue.append(1);
queue.append(10);
queue.append(100);
{
Deque<int>& deque = queue.swapBuffers();
EXPECT_EQ(1, deque.takeFirst());
EXPECT_EQ(10, deque.takeFirst());
EXPECT_EQ(100, deque.takeFirst());
}
queue.append(2);
EXPECT_EQ(2, queue.swapBuffers().takeFirst());
queue.append(3);
EXPECT_EQ(3, queue.swapBuffers().takeFirst());
}
} // namespace
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
'DefaultAllocator.cpp', 'DefaultAllocator.cpp',
'DefaultAllocator.h', 'DefaultAllocator.h',
'Deque.h', 'Deque.h',
'DoubleBufferedDeque.h',
'DoublyLinkedList.h', 'DoublyLinkedList.h',
'DynamicAnnotations.cpp', 'DynamicAnnotations.cpp',
'DynamicAnnotations.h', 'DynamicAnnotations.h',
...@@ -211,6 +212,7 @@ ...@@ -211,6 +212,7 @@
'ArrayBufferBuilderTest.cpp', 'ArrayBufferBuilderTest.cpp',
'CheckedArithmeticTest.cpp', 'CheckedArithmeticTest.cpp',
'DequeTest.cpp', 'DequeTest.cpp',
'DoubleBufferedDequeTest.cpp',
'FunctionalTest.cpp', 'FunctionalTest.cpp',
'HashMapTest.cpp', 'HashMapTest.cpp',
'HashSetTest.cpp', 'HashSetTest.cpp',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment