This revision was automatically updated to reflect the committed changes. Closed by commit rL313637: Use ThreadLauncher to launch TaskPool threads (authored by fjricci).
Changed prior to commit: https://reviews.llvm.org/D37930?vs=115691&id=115844#toc Repository: rL LLVM https://reviews.llvm.org/D37930 Files: lldb/trunk/include/lldb/Host/TaskPool.h lldb/trunk/include/lldb/Utility/TaskPool.h lldb/trunk/lldb.xcodeproj/project.pbxproj lldb/trunk/source/Host/CMakeLists.txt lldb/trunk/source/Host/common/TaskPool.cpp lldb/trunk/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp lldb/trunk/source/Utility/CMakeLists.txt lldb/trunk/source/Utility/TaskPool.cpp lldb/trunk/unittests/Host/CMakeLists.txt lldb/trunk/unittests/Host/TaskPoolTest.cpp lldb/trunk/unittests/Utility/CMakeLists.txt lldb/trunk/unittests/Utility/TaskPoolTest.cpp
Index: lldb/trunk/unittests/Host/TaskPoolTest.cpp =================================================================== --- lldb/trunk/unittests/Host/TaskPoolTest.cpp +++ lldb/trunk/unittests/Host/TaskPoolTest.cpp @@ -0,0 +1,43 @@ +#include "gtest/gtest.h" + +#include "lldb/Host/TaskPool.h" + +TEST(TaskPoolTest, AddTask) { + auto fn = [](int x) { return x * x + 1; }; + + auto f1 = TaskPool::AddTask(fn, 1); + auto f2 = TaskPool::AddTask(fn, 2); + auto f3 = TaskPool::AddTask(fn, 3); + auto f4 = TaskPool::AddTask(fn, 4); + + ASSERT_EQ(10, f3.get()); + ASSERT_EQ(2, f1.get()); + ASSERT_EQ(17, f4.get()); + ASSERT_EQ(5, f2.get()); +} + +TEST(TaskPoolTest, RunTasks) { + std::vector<int> r(4); + + auto fn = [](int x, int &y) { y = x * x + 1; }; + + TaskPool::RunTasks([fn, &r]() { fn(1, r[0]); }, [fn, &r]() { fn(2, r[1]); }, + [fn, &r]() { fn(3, r[2]); }, [fn, &r]() { fn(4, r[3]); }); + + ASSERT_EQ(2, r[0]); + ASSERT_EQ(5, r[1]); + ASSERT_EQ(10, r[2]); + ASSERT_EQ(17, r[3]); +} + +TEST(TaskPoolTest, TaskMap) { + int data[4]; + auto fn = [&data](int x) { data[x] = x * x; }; + + TaskMapOverInt(0, 4, fn); + + ASSERT_EQ(data[0], 0); + ASSERT_EQ(data[1], 1); + ASSERT_EQ(data[2], 4); + ASSERT_EQ(data[3], 9); +} Index: lldb/trunk/unittests/Host/CMakeLists.txt =================================================================== --- lldb/trunk/unittests/Host/CMakeLists.txt +++ lldb/trunk/unittests/Host/CMakeLists.txt @@ -6,6 +6,7 @@ SocketAddressTest.cpp SocketTest.cpp SymbolsTest.cpp + TaskPoolTest.cpp ) if (CMAKE_SYSTEM_NAME MATCHES "Linux|Android") Index: lldb/trunk/unittests/Utility/CMakeLists.txt =================================================================== --- lldb/trunk/unittests/Utility/CMakeLists.txt +++ lldb/trunk/unittests/Utility/CMakeLists.txt @@ -8,7 +8,6 @@ StatusTest.cpp StringExtractorTest.cpp StructuredDataTest.cpp - TaskPoolTest.cpp TildeExpressionResolverTest.cpp TimeoutTest.cpp TimerTest.cpp Index: lldb/trunk/source/Utility/CMakeLists.txt =================================================================== --- lldb/trunk/source/Utility/CMakeLists.txt +++ lldb/trunk/source/Utility/CMakeLists.txt @@ -29,7 +29,6 @@ StringLexer.cpp StringList.cpp StructuredData.cpp - TaskPool.cpp TildeExpressionResolver.cpp Timer.cpp UserID.cpp Index: lldb/trunk/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp =================================================================== --- lldb/trunk/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ lldb/trunk/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -53,7 +53,7 @@ #include "lldb/Target/Language.h" -#include "lldb/Utility/TaskPool.h" +#include "lldb/Host/TaskPool.h" #include "DWARFASTParser.h" #include "DWARFASTParserClang.h" Index: lldb/trunk/source/Host/CMakeLists.txt =================================================================== --- lldb/trunk/source/Host/CMakeLists.txt +++ lldb/trunk/source/Host/CMakeLists.txt @@ -31,6 +31,7 @@ common/SoftwareBreakpoint.cpp common/StringConvert.cpp common/Symbols.cpp + common/TaskPool.cpp common/TCPSocket.cpp common/Terminal.cpp common/ThreadLauncher.cpp Index: lldb/trunk/source/Host/common/TaskPool.cpp =================================================================== --- lldb/trunk/source/Host/common/TaskPool.cpp +++ lldb/trunk/source/Host/common/TaskPool.cpp @@ -0,0 +1,109 @@ +//===--------------------- TaskPool.cpp -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/TaskPool.h" +#include "lldb/Host/ThreadLauncher.h" + +#include <cstdint> // for uint32_t +#include <queue> // for queue +#include <thread> // for thread + +namespace { +class TaskPoolImpl { +public: + static TaskPoolImpl &GetInstance(); + + void AddTask(std::function<void()> &&task_fn); + +private: + TaskPoolImpl(); + + static lldb::thread_result_t WorkerPtr(void *pool); + + static void Worker(TaskPoolImpl *pool); + + std::queue<std::function<void()>> m_tasks; + std::mutex m_tasks_mutex; + uint32_t m_thread_count; +}; + +} // end of anonymous namespace + +TaskPoolImpl &TaskPoolImpl::GetInstance() { + static TaskPoolImpl g_task_pool_impl; + return g_task_pool_impl; +} + +void TaskPool::AddTaskImpl(std::function<void()> &&task_fn) { + TaskPoolImpl::GetInstance().AddTask(std::move(task_fn)); +} + +TaskPoolImpl::TaskPoolImpl() : m_thread_count(0) {} + +void TaskPoolImpl::AddTask(std::function<void()> &&task_fn) { + static const uint32_t max_threads = std::thread::hardware_concurrency(); + const size_t min_stack_size = 8 * 1024 * 1024; + + std::unique_lock<std::mutex> lock(m_tasks_mutex); + m_tasks.emplace(std::move(task_fn)); + if (m_thread_count < max_threads) { + m_thread_count++; + // Note that this detach call needs to happen with the m_tasks_mutex held. + // This prevents the thread + // from exiting prematurely and triggering a linux libc bug + // (https://sourceware.org/bugzilla/show_bug.cgi?id=19951). + lldb_private::ThreadLauncher::LaunchThread("task-pool.worker", WorkerPtr, + this, nullptr, min_stack_size) + .Release(); + } +} + +lldb::thread_result_t TaskPoolImpl::WorkerPtr(void *pool) { + Worker((TaskPoolImpl *)pool); + return 0; +} + +void TaskPoolImpl::Worker(TaskPoolImpl *pool) { + while (true) { + std::unique_lock<std::mutex> lock(pool->m_tasks_mutex); + if (pool->m_tasks.empty()) { + pool->m_thread_count--; + break; + } + + std::function<void()> f = pool->m_tasks.front(); + pool->m_tasks.pop(); + lock.unlock(); + + f(); + } +} + +void TaskMapOverInt(size_t begin, size_t end, + const llvm::function_ref<void(size_t)> &func) { + std::atomic<size_t> idx{begin}; + size_t num_workers = + std::min<size_t>(end, std::thread::hardware_concurrency()); + + auto wrapper = [&idx, end, &func]() { + while (true) { + size_t i = idx.fetch_add(1); + if (i >= end) + break; + func(i); + } + }; + + std::vector<std::future<void>> futures; + futures.reserve(num_workers); + for (size_t i = 0; i < num_workers; i++) + futures.push_back(TaskPool::AddTask(wrapper)); + for (size_t i = 0; i < num_workers; i++) + futures[i].wait(); +} Index: lldb/trunk/include/lldb/Host/TaskPool.h =================================================================== --- lldb/trunk/include/lldb/Host/TaskPool.h +++ lldb/trunk/include/lldb/Host/TaskPool.h @@ -0,0 +1,92 @@ +//===--------------------- TaskPool.h ---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef utility_TaskPool_h_ +#define utility_TaskPool_h_ + +#include "llvm/ADT/STLExtras.h" +#include <functional> // for bind, function +#include <future> +#include <list> +#include <memory> // for make_shared +#include <mutex> // for mutex, unique_lock, condition_variable +#include <type_traits> // for forward, result_of, move + +// Global TaskPool class for running tasks in parallel on a set of worker thread +// created the first +// time the task pool is used. The TaskPool provide no guarantee about the order +// the task will be run +// and about what tasks will run in parallel. None of the task added to the task +// pool should block +// on something (mutex, future, condition variable) what will be set only by the +// completion of an +// other task on the task pool as they may run on the same thread sequentally. +class TaskPool { +public: + // Add a new task to the task pool and return a std::future belonging to the + // newly created task. + // The caller of this function has to wait on the future for this task to + // complete. + template <typename F, typename... Args> + static std::future<typename std::result_of<F(Args...)>::type> + AddTask(F &&f, Args &&... args); + + // Run all of the specified tasks on the task pool and wait until all of them + // are finished + // before returning. This method is intended to be used for small number tasks + // where listing + // them as function arguments is acceptable. For running large number of tasks + // you should use + // AddTask for each task and then call wait() on each returned future. + template <typename... T> static void RunTasks(T &&... tasks); + +private: + TaskPool() = delete; + + template <typename... T> struct RunTaskImpl; + + static void AddTaskImpl(std::function<void()> &&task_fn); +}; + +template <typename F, typename... Args> +std::future<typename std::result_of<F(Args...)>::type> +TaskPool::AddTask(F &&f, Args &&... args) { + auto task_sp = std::make_shared< + std::packaged_task<typename std::result_of<F(Args...)>::type()>>( + std::bind(std::forward<F>(f), std::forward<Args>(args)...)); + + AddTaskImpl([task_sp]() { (*task_sp)(); }); + + return task_sp->get_future(); +} + +template <typename... T> void TaskPool::RunTasks(T &&... tasks) { + RunTaskImpl<T...>::Run(std::forward<T>(tasks)...); +} + +template <typename Head, typename... Tail> +struct TaskPool::RunTaskImpl<Head, Tail...> { + static void Run(Head &&h, Tail &&... t) { + auto f = AddTask(std::forward<Head>(h)); + RunTaskImpl<Tail...>::Run(std::forward<Tail>(t)...); + f.wait(); + } +}; + +template <> struct TaskPool::RunTaskImpl<> { + static void Run() {} +}; + +// Run 'func' on every value from begin .. end-1. Each worker will grab +// 'batch_size' numbers at a time to work on, so for very fast functions, batch +// should be large enough to avoid too much cache line contention. +void TaskMapOverInt(size_t begin, size_t end, + const llvm::function_ref<void(size_t)> &func); + +#endif // #ifndef utility_TaskPool_h_ Index: lldb/trunk/lldb.xcodeproj/project.pbxproj =================================================================== --- lldb/trunk/lldb.xcodeproj/project.pbxproj +++ lldb/trunk/lldb.xcodeproj/project.pbxproj @@ -2664,8 +2664,8 @@ 6D99A3621BBC2F3200979793 /* ArmUnwindInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ArmUnwindInfo.cpp; path = source/Symbol/ArmUnwindInfo.cpp; sourceTree = "<group>"; }; 6D9AB3DC1BB2B74E003F2289 /* TypeMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TypeMap.cpp; path = source/Symbol/TypeMap.cpp; sourceTree = "<group>"; }; 6D9AB3DE1BB2B76B003F2289 /* TypeMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TypeMap.h; path = include/lldb/Symbol/TypeMap.h; sourceTree = "<group>"; }; - 6DEC6F381BD66D750091ABA6 /* TaskPool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TaskPool.cpp; path = source/Utility/TaskPool.cpp; sourceTree = "<group>"; }; - 6DEC6F3A1BD66D950091ABA6 /* TaskPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TaskPool.h; path = include/lldb/Utility/TaskPool.h; sourceTree = "<group>"; }; + 6DEC6F381BD66D750091ABA6 /* TaskPool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TaskPool.cpp; path = source/Host/common/TaskPool.cpp; sourceTree = "<group>"; }; + 6DEC6F3A1BD66D950091ABA6 /* TaskPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TaskPool.h; path = include/lldb/Host/TaskPool.h; sourceTree = "<group>"; }; 8C26C4241C3EA4340031DF7C /* TSanRuntime.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TSanRuntime.cpp; path = TSan/TSanRuntime.cpp; sourceTree = "<group>"; }; 8C26C4251C3EA4340031DF7C /* TSanRuntime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TSanRuntime.h; path = TSan/TSanRuntime.h; sourceTree = "<group>"; }; 8C2D6A52197A1EAF006989C9 /* MemoryHistory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MemoryHistory.cpp; path = source/Target/MemoryHistory.cpp; sourceTree = "<group>"; }; @@ -4413,8 +4413,6 @@ AFEC3361194A8ABA00FF05C6 /* StructuredData.cpp */, 94BA8B6E176F8CA0005A91B5 /* Range.h */, 94BA8B6C176F8C9B005A91B5 /* Range.cpp */, - 6DEC6F3A1BD66D950091ABA6 /* TaskPool.h */, - 6DEC6F381BD66D750091ABA6 /* TaskPool.cpp */, AFF8FF0B1E779D4B003830EF /* TildeExpressionResolver.cpp */, AFF8FF0D1E779D51003830EF /* TildeExpressionResolver.h */, 26BC7D7E10F1B77400F91463 /* Timer.h */, @@ -5248,6 +5246,8 @@ 267A47F21B14115A0021A5BC /* SoftwareBreakpoint.h */, 232CB613191E00CC00EF39FC /* SoftwareBreakpoint.cpp */, 2689B0A4113EE3CD00A4AEDB /* Symbols.h */, + 6DEC6F3A1BD66D950091ABA6 /* TaskPool.h */, + 6DEC6F381BD66D750091ABA6 /* TaskPool.cpp */, 268DA871130095D000C9483A /* Terminal.h */, 3FDFED2319BA6D55009756A7 /* ThreadLauncher.h */, 267A48031B1416080021A5BC /* XML.h */, Index: lldb/trunk/unittests/Utility/TaskPoolTest.cpp =================================================================== --- lldb/trunk/unittests/Utility/TaskPoolTest.cpp +++ lldb/trunk/unittests/Utility/TaskPoolTest.cpp @@ -1,43 +0,0 @@ -#include "gtest/gtest.h" - -#include "lldb/Utility/TaskPool.h" - -TEST(TaskPoolTest, AddTask) { - auto fn = [](int x) { return x * x + 1; }; - - auto f1 = TaskPool::AddTask(fn, 1); - auto f2 = TaskPool::AddTask(fn, 2); - auto f3 = TaskPool::AddTask(fn, 3); - auto f4 = TaskPool::AddTask(fn, 4); - - ASSERT_EQ(10, f3.get()); - ASSERT_EQ(2, f1.get()); - ASSERT_EQ(17, f4.get()); - ASSERT_EQ(5, f2.get()); -} - -TEST(TaskPoolTest, RunTasks) { - std::vector<int> r(4); - - auto fn = [](int x, int &y) { y = x * x + 1; }; - - TaskPool::RunTasks([fn, &r]() { fn(1, r[0]); }, [fn, &r]() { fn(2, r[1]); }, - [fn, &r]() { fn(3, r[2]); }, [fn, &r]() { fn(4, r[3]); }); - - ASSERT_EQ(2, r[0]); - ASSERT_EQ(5, r[1]); - ASSERT_EQ(10, r[2]); - ASSERT_EQ(17, r[3]); -} - -TEST(TaskPoolTest, TaskMap) { - int data[4]; - auto fn = [&data](int x) { data[x] = x * x; }; - - TaskMapOverInt(0, 4, fn); - - ASSERT_EQ(data[0], 0); - ASSERT_EQ(data[1], 1); - ASSERT_EQ(data[2], 4); - ASSERT_EQ(data[3], 9); -} Index: lldb/trunk/source/Utility/TaskPool.cpp =================================================================== --- lldb/trunk/source/Utility/TaskPool.cpp +++ lldb/trunk/source/Utility/TaskPool.cpp @@ -1,98 +0,0 @@ -//===--------------------- TaskPool.cpp -------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "lldb/Utility/TaskPool.h" - -#include <cstdint> // for uint32_t -#include <queue> // for queue -#include <thread> // for thread - -namespace { -class TaskPoolImpl { -public: - static TaskPoolImpl &GetInstance(); - - void AddTask(std::function<void()> &&task_fn); - -private: - TaskPoolImpl(); - - static void Worker(TaskPoolImpl *pool); - - std::queue<std::function<void()>> m_tasks; - std::mutex m_tasks_mutex; - uint32_t m_thread_count; -}; - -} // end of anonymous namespace - -TaskPoolImpl &TaskPoolImpl::GetInstance() { - static TaskPoolImpl g_task_pool_impl; - return g_task_pool_impl; -} - -void TaskPool::AddTaskImpl(std::function<void()> &&task_fn) { - TaskPoolImpl::GetInstance().AddTask(std::move(task_fn)); -} - -TaskPoolImpl::TaskPoolImpl() : m_thread_count(0) {} - -void TaskPoolImpl::AddTask(std::function<void()> &&task_fn) { - static const uint32_t max_threads = std::thread::hardware_concurrency(); - - std::unique_lock<std::mutex> lock(m_tasks_mutex); - m_tasks.emplace(std::move(task_fn)); - if (m_thread_count < max_threads) { - m_thread_count++; - // Note that this detach call needs to happen with the m_tasks_mutex held. - // This prevents the thread - // from exiting prematurely and triggering a linux libc bug - // (https://sourceware.org/bugzilla/show_bug.cgi?id=19951). - std::thread(Worker, this).detach(); - } -} - -void TaskPoolImpl::Worker(TaskPoolImpl *pool) { - while (true) { - std::unique_lock<std::mutex> lock(pool->m_tasks_mutex); - if (pool->m_tasks.empty()) { - pool->m_thread_count--; - break; - } - - std::function<void()> f = pool->m_tasks.front(); - pool->m_tasks.pop(); - lock.unlock(); - - f(); - } -} - -void TaskMapOverInt(size_t begin, size_t end, - const llvm::function_ref<void(size_t)> &func) { - std::atomic<size_t> idx{begin}; - size_t num_workers = - std::min<size_t>(end, std::thread::hardware_concurrency()); - - auto wrapper = [&idx, end, &func]() { - while (true) { - size_t i = idx.fetch_add(1); - if (i >= end) - break; - func(i); - } - }; - - std::vector<std::future<void>> futures; - futures.reserve(num_workers); - for (size_t i = 0; i < num_workers; i++) - futures.push_back(TaskPool::AddTask(wrapper)); - for (size_t i = 0; i < num_workers; i++) - futures[i].wait(); -} Index: lldb/trunk/include/lldb/Utility/TaskPool.h =================================================================== --- lldb/trunk/include/lldb/Utility/TaskPool.h +++ lldb/trunk/include/lldb/Utility/TaskPool.h @@ -1,92 +0,0 @@ -//===--------------------- TaskPool.h ---------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#ifndef utility_TaskPool_h_ -#define utility_TaskPool_h_ - -#include "llvm/ADT/STLExtras.h" -#include <functional> // for bind, function -#include <future> -#include <list> -#include <memory> // for make_shared -#include <mutex> // for mutex, unique_lock, condition_variable -#include <type_traits> // for forward, result_of, move - -// Global TaskPool class for running tasks in parallel on a set of worker thread -// created the first -// time the task pool is used. The TaskPool provide no guarantee about the order -// the task will be run -// and about what tasks will run in parallel. None of the task added to the task -// pool should block -// on something (mutex, future, condition variable) what will be set only by the -// completion of an -// other task on the task pool as they may run on the same thread sequentally. -class TaskPool { -public: - // Add a new task to the task pool and return a std::future belonging to the - // newly created task. - // The caller of this function has to wait on the future for this task to - // complete. - template <typename F, typename... Args> - static std::future<typename std::result_of<F(Args...)>::type> - AddTask(F &&f, Args &&... args); - - // Run all of the specified tasks on the task pool and wait until all of them - // are finished - // before returning. This method is intended to be used for small number tasks - // where listing - // them as function arguments is acceptable. For running large number of tasks - // you should use - // AddTask for each task and then call wait() on each returned future. - template <typename... T> static void RunTasks(T &&... tasks); - -private: - TaskPool() = delete; - - template <typename... T> struct RunTaskImpl; - - static void AddTaskImpl(std::function<void()> &&task_fn); -}; - -template <typename F, typename... Args> -std::future<typename std::result_of<F(Args...)>::type> -TaskPool::AddTask(F &&f, Args &&... args) { - auto task_sp = std::make_shared< - std::packaged_task<typename std::result_of<F(Args...)>::type()>>( - std::bind(std::forward<F>(f), std::forward<Args>(args)...)); - - AddTaskImpl([task_sp]() { (*task_sp)(); }); - - return task_sp->get_future(); -} - -template <typename... T> void TaskPool::RunTasks(T &&... tasks) { - RunTaskImpl<T...>::Run(std::forward<T>(tasks)...); -} - -template <typename Head, typename... Tail> -struct TaskPool::RunTaskImpl<Head, Tail...> { - static void Run(Head &&h, Tail &&... t) { - auto f = AddTask(std::forward<Head>(h)); - RunTaskImpl<Tail...>::Run(std::forward<Tail>(t)...); - f.wait(); - } -}; - -template <> struct TaskPool::RunTaskImpl<> { - static void Run() {} -}; - -// Run 'func' on every value from begin .. end-1. Each worker will grab -// 'batch_size' numbers at a time to work on, so for very fast functions, batch -// should be large enough to avoid too much cache line contention. -void TaskMapOverInt(size_t begin, size_t end, - const llvm::function_ref<void(size_t)> &func); - -#endif // #ifndef utility_TaskPool_h_
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits