<div dir="ltr">Dmitry,<div><br></div><div>I think this breaks sanitizer-windows build:</div><div><br></div><div><a href="http://lab.llvm.org:8011/builders/sanitizer-windows/builds/24305/steps/run%20tests/logs/stdio">http://lab.llvm.org:8011/builders/sanitizer-windows/builds/24305/steps/run%20tests/logs/stdio</a><br></div><div><br></div><div>First build with the problem: <a href="http://lab.llvm.org:8011/builders/sanitizer-windows/builds/24286">http://lab.llvm.org:8011/builders/sanitizer-windows/builds/24286</a></div><div><br></div></div><br><div class="gmail_quote"><div dir="ltr">On Tue, Jun 21, 2016 at 5:36 AM Dmitry Vyukov via llvm-commits <<a href="mailto:llvm-commits@lists.llvm.org">llvm-commits@lists.llvm.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Author: dvyukov<br>
Date: Tue Jun 21 07:29:18 2016<br>
New Revision: 273260<br>
<br>
URL: <a href="http://llvm.org/viewvc/llvm-project?rev=273260&view=rev" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project?rev=273260&view=rev</a><br>
Log:<br>
[asan] add primitives that allow coroutine implementations<br>
<br>
This patch adds the __sanitizer_start_switch_fiber and<br>
__sanitizer_finish_switch_fiber methods inspired from what can be found here<br>
<a href="https://github.com/facebook/folly/commit/2ea64dd24946cbc9f3f4ac3f6c6b98a486c56e73" rel="noreferrer" target="_blank">https://github.com/facebook/folly/commit/2ea64dd24946cbc9f3f4ac3f6c6b98a486c56e73</a> .<br>
<br>
These methods are needed when the compiled software needs to implement<br>
coroutines, fibers or the like. Without a way to annotate them, when the program<br>
jumps to a stack that is not the thread stack, __asan_handle_no_return shows a<br>
warning about that, and the fake stack mechanism may free fake frames that are<br>
still in use.<br>
<br>
Author: blastrock (Philippe Daouadi)<br>
Reviewed in <a href="http://reviews.llvm.org/D20913" rel="noreferrer" target="_blank">http://reviews.llvm.org/D20913</a><br>
<br>
<br>
Added:<br>
compiler-rt/trunk/test/asan/TestCases/Linux/swapcontext_annotation.cc<br>
Modified:<br>
compiler-rt/trunk/include/sanitizer/common_interface_defs.h<br>
compiler-rt/trunk/lib/asan/asan_thread.cc<br>
compiler-rt/trunk/lib/asan/asan_thread.h<br>
<br>
Modified: compiler-rt/trunk/include/sanitizer/common_interface_defs.h<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/include/sanitizer/common_interface_defs.h?rev=273260&r1=273259&r2=273260&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/include/sanitizer/common_interface_defs.h?rev=273260&r1=273259&r2=273260&view=diff</a><br>
==============================================================================<br>
--- compiler-rt/trunk/include/sanitizer/common_interface_defs.h (original)<br>
+++ compiler-rt/trunk/include/sanitizer/common_interface_defs.h Tue Jun 21 07:29:18 2016<br>
@@ -139,6 +139,26 @@ extern "C" {<br>
// `top_percent` should be between 1 and 100.<br>
// Experimental feature currently available only with asan on Linux/x86_64.<br>
void __sanitizer_print_memory_profile(size_t top_percent);<br>
+<br>
+ // Fiber annotation interface.<br>
+ // Before switching to a different stack, one must call<br>
+ // __sanitizer_start_switch_fiber with a pointer to the bottom of the<br>
+ // destination stack and its size. When code starts running on the new stack,<br>
+ // it must call __sanitizer_finish_switch_fiber to finalize the switch.<br>
+ // The start_switch function takes a void** to store the current fake stack if<br>
+ // there is one (it is needed when detect_stack_use_after_return is enabled).<br>
+ // When restoring a stack, this pointer must be given to the finish_switch<br>
+ // function. In most cases, this void* can be stored on the stack just before<br>
+ // switching. When leaving a fiber definitely, null must be passed as first<br>
+ // argument to the start_switch function so that the fake stack is destroyed.<br>
+ // If you do not want support for stack use-after-return detection, you can<br>
+ // always pass null to these two functions.<br>
+ // Note that the fake stack mechanism is disabled during fiber switch, so if a<br>
+ // signal callback runs during the switch, it will not benefit from the stack<br>
+ // use-after-return detection.<br>
+ void __sanitizer_start_switch_fiber(void **fake_stack_save,<br>
+ const void *bottom, size_t size);<br>
+ void __sanitizer_finish_switch_fiber(void *fake_stack_save);<br>
#ifdef __cplusplus<br>
} // extern "C"<br>
#endif<br>
<br>
Modified: compiler-rt/trunk/lib/asan/asan_thread.cc<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/asan/asan_thread.cc?rev=273260&r1=273259&r2=273260&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/asan/asan_thread.cc?rev=273260&r1=273259&r2=273260&view=diff</a><br>
==============================================================================<br>
--- compiler-rt/trunk/lib/asan/asan_thread.cc (original)<br>
+++ compiler-rt/trunk/lib/asan/asan_thread.cc Tue Jun 21 07:29:18 2016<br>
@@ -120,6 +120,71 @@ void AsanThread::Destroy() {<br>
DTLS_Destroy();<br>
}<br>
<br>
+void AsanThread::StartSwitchFiber(FakeStack **fake_stack_save, uptr bottom,<br>
+ uptr size) {<br>
+ if (atomic_load(&stack_switching_, memory_order_relaxed)) {<br>
+ Report("ERROR: starting fiber switch while in fiber switch\n");<br>
+ Die();<br>
+ }<br>
+<br>
+ next_stack_bottom_ = bottom;<br>
+ next_stack_top_ = bottom + size;<br>
+ atomic_store(&stack_switching_, 1, memory_order_release);<br>
+<br>
+ FakeStack *current_fake_stack = fake_stack_;<br>
+ if (fake_stack_save)<br>
+ *fake_stack_save = fake_stack_;<br>
+ fake_stack_ = nullptr;<br>
+ SetTLSFakeStack(nullptr);<br>
+ // if fake_stack_save is null, the fiber will die, delete the fakestack<br>
+ if (!fake_stack_save && current_fake_stack)<br>
+ current_fake_stack->Destroy(this->tid());<br>
+}<br>
+<br>
+void AsanThread::FinishSwitchFiber(FakeStack *fake_stack_save) {<br>
+ if (!atomic_load(&stack_switching_, memory_order_relaxed)) {<br>
+ Report("ERROR: finishing a fiber switch that has not started\n");<br>
+ Die();<br>
+ }<br>
+<br>
+ if (fake_stack_save) {<br>
+ SetTLSFakeStack(fake_stack_save);<br>
+ fake_stack_ = fake_stack_save;<br>
+ }<br>
+<br>
+ stack_bottom_ = next_stack_bottom_;<br>
+ stack_top_ = next_stack_top_;<br>
+ atomic_store(&stack_switching_, 0, memory_order_release);<br>
+ next_stack_top_ = 0;<br>
+ next_stack_bottom_ = 0;<br>
+}<br>
+<br>
+inline AsanThread::StackBounds AsanThread::GetStackBounds() const {<br>
+ if (!atomic_load(&stack_switching_, memory_order_acquire))<br>
+ return StackBounds{stack_bottom_, stack_top_}; // NOLINT<br>
+ char local;<br>
+ const uptr cur_stack = (uptr)&local;<br>
+ // Note: need to check next stack first, because FinishSwitchFiber<br>
+ // may be in process of overwriting stack_top_/bottom_. But in such case<br>
+ // we are already on the next stack.<br>
+ if (cur_stack >= next_stack_bottom_ && cur_stack < next_stack_top_)<br>
+ return StackBounds{next_stack_bottom_, next_stack_top_}; // NOLINT<br>
+ return StackBounds{stack_bottom_, stack_top_}; // NOLINT<br>
+}<br>
+<br>
+uptr AsanThread::stack_top() {<br>
+ return GetStackBounds().top;<br>
+}<br>
+<br>
+uptr AsanThread::stack_bottom() {<br>
+ return GetStackBounds().bottom;<br>
+}<br>
+<br>
+uptr AsanThread::stack_size() {<br>
+ const auto bounds = GetStackBounds();<br>
+ return bounds.top - bounds.bottom;<br>
+}<br>
+<br>
// We want to create the FakeStack lazyly on the first use, but not eralier<br>
// than the stack size is known and the procedure has to be async-signal safe.<br>
FakeStack *AsanThread::AsyncSignalSafeLazyInitFakeStack() {<br>
@@ -150,6 +215,8 @@ FakeStack *AsanThread::AsyncSignalSafeLa<br>
}<br>
<br>
void AsanThread::Init() {<br>
+ next_stack_top_ = next_stack_bottom_ = 0;<br>
+ atomic_store(&stack_switching_, false, memory_order_release);<br>
fake_stack_ = nullptr; // Will be initialized lazily if needed.<br>
CHECK_EQ(this->stack_size(), 0U);<br>
SetThreadStackAndTls();<br>
@@ -195,9 +262,10 @@ thread_return_t AsanThread::ThreadStart(<br>
<br>
void AsanThread::SetThreadStackAndTls() {<br>
uptr tls_size = 0;<br>
- GetThreadStackAndTls(tid() == 0, &stack_bottom_, &stack_size_, &tls_begin_,<br>
- &tls_size);<br>
- stack_top_ = stack_bottom_ + stack_size_;<br>
+ uptr stack_size = 0;<br>
+ GetThreadStackAndTls(tid() == 0, const_cast<uptr *>(&stack_bottom_),<br>
+ const_cast<uptr *>(&stack_size), &tls_begin_, &tls_size);<br>
+ stack_top_ = stack_bottom_ + stack_size;<br>
tls_end_ = tls_begin_ + tls_size;<br>
dtls_ = DTLS_Get();<br>
<br>
@@ -250,6 +318,11 @@ bool AsanThread::GetStackFrameAccessByAd<br>
return true;<br>
}<br>
<br>
+bool AsanThread::AddrIsInStack(uptr addr) {<br>
+ const auto bounds = GetStackBounds();<br>
+ return addr >= bounds.bottom && addr < bounds.top;<br>
+}<br>
+<br>
static bool ThreadStackContainsAddress(ThreadContextBase *tctx_base,<br>
void *addr) {<br>
AsanThreadContext *tctx = static_cast<AsanThreadContext*>(tctx_base);<br>
@@ -357,3 +430,29 @@ void EnsureMainThreadIDIsCorrect() {<br>
__asan::EnsureMainThreadIDIsCorrect();<br>
}<br>
} // namespace __lsan<br>
+<br>
+// ---------------------- Interface ---------------- {{{1<br>
+using namespace __asan; // NOLINT<br>
+<br>
+extern "C" {<br>
+SANITIZER_INTERFACE_ATTRIBUTE<br>
+void __sanitizer_start_switch_fiber(void **fakestacksave, const void *bottom,<br>
+ uptr size) {<br>
+ AsanThread *t = GetCurrentThread();<br>
+ if (!t) {<br>
+ VReport(1, "__asan_start_switch_fiber called from unknown thread\n");<br>
+ return;<br>
+ }<br>
+ t->StartSwitchFiber((FakeStack**)fakestacksave, (uptr)bottom, size);<br>
+}<br>
+<br>
+SANITIZER_INTERFACE_ATTRIBUTE<br>
+void __sanitizer_finish_switch_fiber(void* fakestack) {<br>
+ AsanThread *t = GetCurrentThread();<br>
+ if (!t) {<br>
+ VReport(1, "__asan_finish_switch_fiber called from unknown thread\n");<br>
+ return;<br>
+ }<br>
+ t->FinishSwitchFiber((FakeStack*)fakestack);<br>
+}<br>
+}<br>
<br>
Modified: compiler-rt/trunk/lib/asan/asan_thread.h<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/asan/asan_thread.h?rev=273260&r1=273259&r2=273260&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/asan/asan_thread.h?rev=273260&r1=273259&r2=273260&view=diff</a><br>
==============================================================================<br>
--- compiler-rt/trunk/lib/asan/asan_thread.h (original)<br>
+++ compiler-rt/trunk/lib/asan/asan_thread.h Tue Jun 21 07:29:18 2016<br>
@@ -66,9 +66,9 @@ class AsanThread {<br>
thread_return_t ThreadStart(uptr os_id,<br>
atomic_uintptr_t *signal_thread_is_registered);<br>
<br>
- uptr stack_top() { return stack_top_; }<br>
- uptr stack_bottom() { return stack_bottom_; }<br>
- uptr stack_size() { return stack_size_; }<br>
+ uptr stack_top();<br>
+ uptr stack_bottom();<br>
+ uptr stack_size();<br>
uptr tls_begin() { return tls_begin_; }<br>
uptr tls_end() { return tls_end_; }<br>
DTLS *dtls() { return dtls_; }<br>
@@ -83,9 +83,7 @@ class AsanThread {<br>
};<br>
bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);<br>
<br>
- bool AddrIsInStack(uptr addr) {<br>
- return addr >= stack_bottom_ && addr < stack_top_;<br>
- }<br>
+ bool AddrIsInStack(uptr addr);<br>
<br>
void DeleteFakeStack(int tid) {<br>
if (!fake_stack_) return;<br>
@@ -95,13 +93,19 @@ class AsanThread {<br>
t->Destroy(tid);<br>
}<br>
<br>
+ void StartSwitchFiber(FakeStack **fake_stack_save, uptr bottom, uptr size);<br>
+ void FinishSwitchFiber(FakeStack *fake_stack_save);<br>
+<br>
bool has_fake_stack() {<br>
- return (reinterpret_cast<uptr>(fake_stack_) > 1);<br>
+ return !atomic_load(&stack_switching_, memory_order_relaxed) &&<br>
+ (reinterpret_cast<uptr>(fake_stack_) > 1);<br>
}<br>
<br>
FakeStack *fake_stack() {<br>
if (!__asan_option_detect_stack_use_after_return)<br>
return nullptr;<br>
+ if (atomic_load(&stack_switching_, memory_order_relaxed))<br>
+ return nullptr;<br>
if (!has_fake_stack())<br>
return AsyncSignalSafeLazyInitFakeStack();<br>
return fake_stack_;<br>
@@ -127,14 +131,24 @@ class AsanThread {<br>
void ClearShadowForThreadStackAndTLS();<br>
FakeStack *AsyncSignalSafeLazyInitFakeStack();<br>
<br>
+ struct StackBounds {<br>
+ uptr bottom;<br>
+ uptr top;<br>
+ };<br>
+ StackBounds GetStackBounds() const;<br>
+<br>
AsanThreadContext *context_;<br>
thread_callback_t start_routine_;<br>
void *arg_;<br>
+<br>
uptr stack_top_;<br>
uptr stack_bottom_;<br>
- // stack_size_ == stack_top_ - stack_bottom_;<br>
- // It needs to be set in a async-signal-safe manner.<br>
- uptr stack_size_;<br>
+ // these variables are used when the thread is about to switch stack<br>
+ uptr next_stack_top_;<br>
+ uptr next_stack_bottom_;<br>
+ // true if switching is in progress<br>
+ atomic_uint8_t stack_switching_;<br>
+<br>
uptr tls_begin_;<br>
uptr tls_end_;<br>
DTLS *dtls_;<br>
<br>
Added: compiler-rt/trunk/test/asan/TestCases/Linux/swapcontext_annotation.cc<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/test/asan/TestCases/Linux/swapcontext_annotation.cc?rev=273260&view=auto" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/test/asan/TestCases/Linux/swapcontext_annotation.cc?rev=273260&view=auto</a><br>
==============================================================================<br>
--- compiler-rt/trunk/test/asan/TestCases/Linux/swapcontext_annotation.cc (added)<br>
+++ compiler-rt/trunk/test/asan/TestCases/Linux/swapcontext_annotation.cc Tue Jun 21 07:29:18 2016<br>
@@ -0,0 +1,178 @@<br>
+// Check that ASan plays well with annotated makecontext/swapcontext.<br>
+<br>
+// RUN: %clangxx_asan -lpthread -O0 %s -o %t && %run %t 2>&1 | FileCheck %s<br>
+// RUN: %clangxx_asan -lpthread -O1 %s -o %t && %run %t 2>&1 | FileCheck %s<br>
+// RUN: %clangxx_asan -lpthread -O2 %s -o %t && %run %t 2>&1 | FileCheck %s<br>
+// RUN: %clangxx_asan -lpthread -O3 %s -o %t && %run %t 2>&1 | FileCheck %s<br>
+//<br>
+// This test is too subtle to try on non-x86 arch for now.<br>
+// REQUIRES: x86_64-supported-target,i386-supported-target<br>
+<br>
+#include <pthread.h><br>
+#include <setjmp.h><br>
+#include <stdio.h><br>
+#include <sys/time.h><br>
+#include <ucontext.h><br>
+#include <unistd.h><br>
+<br>
+#include <sanitizer/common_interface_defs.h><br>
+<br>
+ucontext_t orig_context;<br>
+ucontext_t child_context;<br>
+ucontext_t next_child_context;<br>
+<br>
+char *next_child_stack;<br>
+<br>
+const int kStackSize = 1 << 20;<br>
+<br>
+void *main_thread_stack;<br>
+size_t main_thread_stacksize;<br>
+<br>
+__attribute__((noinline, noreturn)) void LongJump(jmp_buf env) {<br>
+ longjmp(env, 1);<br>
+ _exit(1);<br>
+}<br>
+<br>
+// Simulate __asan_handle_no_return().<br>
+__attribute__((noinline)) void CallNoReturn() {<br>
+ jmp_buf env;<br>
+ if (setjmp(env) != 0) return;<br>
+<br>
+ LongJump(env);<br>
+ _exit(1);<br>
+}<br>
+<br>
+void NextChild() {<br>
+ CallNoReturn();<br>
+ __sanitizer_finish_switch_fiber();<br>
+<br>
+ char x[32] = {0}; // Stack gets poisoned.<br>
+ printf("NextChild: %p\n", x);<br>
+<br>
+ CallNoReturn();<br>
+<br>
+ __sanitizer_start_switch_fiber(main_thread_stack, main_thread_stacksize);<br>
+ CallNoReturn();<br>
+ if (swapcontext(&next_child_context, &orig_context) < 0) {<br>
+ perror("swapcontext");<br>
+ _exit(1);<br>
+ }<br>
+}<br>
+<br>
+void Child(int mode) {<br>
+ CallNoReturn();<br>
+ __sanitizer_finish_switch_fiber();<br>
+ char x[32] = {0}; // Stack gets poisoned.<br>
+ printf("Child: %p\n", x);<br>
+ CallNoReturn();<br>
+ // (a) Do nothing, just return to parent function.<br>
+ // (b) Jump into the original function. Stack remains poisoned unless we do<br>
+ // something.<br>
+ // (c) Jump to another function which will then jump back to the main function<br>
+ if (mode == 0) {<br>
+ __sanitizer_start_switch_fiber(main_thread_stack, main_thread_stacksize);<br>
+ CallNoReturn();<br>
+ } else if (mode == 1) {<br>
+ __sanitizer_start_switch_fiber(main_thread_stack, main_thread_stacksize);<br>
+ CallNoReturn();<br>
+ if (swapcontext(&child_context, &orig_context) < 0) {<br>
+ perror("swapcontext");<br>
+ _exit(1);<br>
+ }<br>
+ } else if (mode == 2) {<br>
+ getcontext(&next_child_context);<br>
+ next_child_context.uc_stack.ss_sp = next_child_stack;<br>
+ next_child_context.uc_stack.ss_size = kStackSize / 2;<br>
+ makecontext(&next_child_context, (void (*)())NextChild, 0);<br>
+ __sanitizer_start_switch_fiber(next_child_context.uc_stack.ss_sp,<br>
+ next_child_context.uc_stack.ss_size);<br>
+ CallNoReturn();<br>
+ if (swapcontext(&child_context, &next_child_context) < 0) {<br>
+ perror("swapcontext");<br>
+ _exit(1);<br>
+ }<br>
+ }<br>
+}<br>
+<br>
+int Run(int arg, int mode, char *child_stack) {<br>
+ printf("Child stack: %p\n", child_stack);<br>
+ // Setup child context.<br>
+ getcontext(&child_context);<br>
+ child_context.uc_stack.ss_sp = child_stack;<br>
+ child_context.uc_stack.ss_size = kStackSize / 2;<br>
+ if (mode == 0) {<br>
+ child_context.uc_link = &orig_context;<br>
+ }<br>
+ makecontext(&child_context, (void (*)())Child, 1, mode);<br>
+ CallNoReturn();<br>
+ __sanitizer_start_switch_fiber(child_context.uc_stack.ss_sp,<br>
+ child_context.uc_stack.ss_size);<br>
+ CallNoReturn();<br>
+ if (swapcontext(&orig_context, &child_context) < 0) {<br>
+ perror("swapcontext");<br>
+ _exit(1);<br>
+ }<br>
+ CallNoReturn();<br>
+ __sanitizer_finish_switch_fiber();<br>
+ CallNoReturn();<br>
+<br>
+ // Touch childs's stack to make sure it's unpoisoned.<br>
+ for (int i = 0; i < kStackSize; i++) {<br>
+ child_stack[i] = i;<br>
+ }<br>
+ return child_stack[arg];<br>
+}<br>
+<br>
+void handler(int sig) { CallNoReturn(); }<br>
+<br>
+void InitStackBounds() {<br>
+ pthread_attr_t attr;<br>
+ pthread_attr_init(&attr);<br>
+ pthread_getattr_np(pthread_self(), &attr);<br>
+ pthread_attr_getstack(&attr, &main_thread_stack, &main_thread_stacksize);<br>
+ pthread_attr_destroy(&attr);<br>
+}<br>
+<br>
+int main(int argc, char **argv) {<br>
+ InitStackBounds();<br>
+<br>
+ // set up a signal that will spam and trigger __asan_handle_no_return at<br>
+ // tricky moments<br>
+ struct sigaction act = {};<br>
+ act.sa_handler = &handler;<br>
+ if (sigaction(SIGPROF, &act, 0)) {<br>
+ perror("sigaction");<br>
+ _exit(1);<br>
+ }<br>
+<br>
+ itimerval t;<br>
+ t.it_interval.tv_sec = 0;<br>
+ t.it_interval.tv_usec = 10;<br>
+ t.it_value = t.it_interval;<br>
+ if (setitimer(ITIMER_PROF, &t, 0)) {<br>
+ perror("setitimer");<br>
+ _exit(1);<br>
+ }<br>
+<br>
+ char *heap = new char[kStackSize + 1];<br>
+ next_child_stack = new char[kStackSize + 1];<br>
+ char stack[kStackSize + 1];<br>
+ // CHECK: WARNING: ASan doesn't fully support makecontext/swapcontext<br>
+ int ret = 0;<br>
+ // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return<br>
+ for (unsigned int i = 0; i < 30; ++i) {<br>
+ ret += Run(argc - 1, 0, stack);<br>
+ ret += Run(argc - 1, 1, stack);<br>
+ ret += Run(argc - 1, 2, stack);<br>
+ ret += Run(argc - 1, 0, heap);<br>
+ ret += Run(argc - 1, 1, heap);<br>
+ ret += Run(argc - 1, 2, heap);<br>
+ }<br>
+ // CHECK: Test passed<br>
+ printf("Test passed\n");<br>
+<br>
+ delete[] heap;<br>
+ delete[] next_child_stack;<br>
+<br>
+ return ret;<br>
+}<br>
<br>
<br>
_______________________________________________<br>
llvm-commits mailing list<br>
<a href="mailto:llvm-commits@lists.llvm.org" target="_blank">llvm-commits@lists.llvm.org</a><br>
<a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits</a><br>
</blockquote></div><div dir="ltr">-- <br></div><div data-smartmail="gmail_signature">Mike<br>Sent from phone</div>