[libc] [lld] [llvm] [libc][rework] arc4random with vDSO support (PR #151361)

Schrodinger ZHU Yifan via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 31 19:15:19 PDT 2025


https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/151361

>From ec5d9715dfc448ac2329dff4a48b344d6954c185 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yifanzhu at rochester.edu>
Date: Wed, 30 Jul 2025 12:47:24 -0400
Subject: [PATCH 1/3] wip

---
 libc/src/__support/aba_ptr.h    | 70 ++++++++++++++++++++++++++++++
 libc/src/__support/mpmc_stack.h | 75 +++++++++++++++++++++++++++++++++
 2 files changed, 145 insertions(+)
 create mode 100644 libc/src/__support/aba_ptr.h
 create mode 100644 libc/src/__support/mpmc_stack.h

diff --git a/libc/src/__support/aba_ptr.h b/libc/src/__support/aba_ptr.h
new file mode 100644
index 0000000000000..c702aae017502
--- /dev/null
+++ b/libc/src/__support/aba_ptr.h
@@ -0,0 +1,70 @@
+//===-- Transactional Ptr for ABA prevention --------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_TAGGED_POINTER_H
+#define LLVM_LIBC_SRC___SUPPORT_TAGGED_POINTER_H
+
+#include "src/__support/common.h"
+#include "src/__support/threads/sleep.h"
+
+#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_16
+#define LIBC_ABA_PTR_IS_ATOMIC true
+#else
+#define LIBC_ABA_PTR_IS_ATOMIC false
+#endif
+
+namespace LIBC_NAMESPACE_DECL {
+
+template <class T, bool IsAtomic> struct AbaPtrImpl {
+  union Impl {
+    struct alignas(2 * alignof(void *)) Atomic {
+      T *ptr;
+      __SIZE_TYPE__ tag;
+    } atomic;
+    struct Mutex {
+      T *ptr;
+      bool locked;
+    } mutex;
+  } impl;
+
+  LIBC_INLINE constexpr AbaPtrImpl(T *ptr)
+      : impl(IsAtomic ? Impl{.atomic{ptr, 0}} : Impl{.mutex{ptr, false}}) {}
+
+  /// User must guarantee that operation is redoable.
+  template <class Op> LIBC_INLINE void transaction(Op &&op) {
+    if constexpr (IsAtomic) {
+      for (;;) {
+        typename Impl::Atomic snapshot, next;
+        __atomic_load(&impl.atomic, &snapshot, __ATOMIC_RELAXED);
+        next.ptr = op(snapshot.ptr);
+        // Wrapping add for unsigned integers.
+        next.tag = snapshot.tag + 1;
+        if (__atomic_compare_exchange(&impl.atomic, &snapshot, &next, true,
+                                      __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
+          return;
+        }
+      }
+    } else {
+      // Acquire the lock.
+      while (__atomic_exchange_n(&impl.mutex.locked, true, __ATOMIC_ACQUIRE)) {
+        while (__atomic_load_n(&impl.mutex.locked, __ATOMIC_RELAXED)) {
+          LIBC_NAMESPACE::sleep_briefly();
+        }
+      }
+      impl.mutex.ptr = op(impl.mutex.ptr);
+      // Release the lock.
+      __atomic_store_n(&impl.mutex.locked, false, __ATOMIC_RELEASE);
+    }
+  }
+};
+
+template <class T> using AbaPtr = AbaPtrImpl<T, LIBC_ABA_PTR_IS_ATOMIC>;
+} // namespace LIBC_NAMESPACE_DECL
+
+#undef LIBC_ABA_PTR_IS_ATOMIC
+#endif
diff --git a/libc/src/__support/mpmc_stack.h b/libc/src/__support/mpmc_stack.h
new file mode 100644
index 0000000000000..4892c3926b4b0
--- /dev/null
+++ b/libc/src/__support/mpmc_stack.h
@@ -0,0 +1,75 @@
+//===-- Simple Lock-free MPMC Stack -----------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_MPMC_STACK_H
+#define LLVM_LIBC_SRC___SUPPORT_MPMC_STACK_H
+
+#include "src/__support/CPP/atomic.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/aba_ptr.h"
+
+namespace LIBC_NAMESPACE_DECL {
+template <class T> class MPMCStack {
+  struct Node {
+    cpp::Atomic<size_t> visitor;
+    AbaPtr<Node> next;
+    T value;
+
+    LIBC_INLINE Node(T val) : visitor(0), next(nullptr), value(val) {}
+  };
+  AbaPtr<Node> head;
+
+public:
+  static_assert(cpp::is_copy_constructible<T>::value,
+                "T must be copy constructible");
+  LIBC_INLINE MPMCStack() : head(nullptr) {}
+  LIBC_INLINE bool push(T value) {
+    AllocChecker ac;
+    Node *new_node = new Node(value, ac);
+    if (!ac) {
+      return false;
+    }
+    head.transaction([new_node](Node *old_head) {
+      new_node->next = old_head;
+      return new_node;
+    });
+    return true;
+  }
+  LIBC_INLINE cpp::optional<T> pop() {
+    cpp::optional<T> res;
+    Node *node;
+    head.transaction([&](Node *current_head) {
+      if (!current_head) {
+        res = cpp::nullopt;
+        return nullptr;
+      }
+      node = current_head;
+      node->visitor.fetch_add(1);
+      res = node->value;
+      auto next = node->next;
+      node->visitor.fetch_sub(1);
+      return next;
+    });
+    // On a successful transaction, a node is popped by us. So we must delete
+    // it. When we are at here, no one else can acquire
+    // new reference to the node, but we still need to wait until other threads
+    // inside the transaction who may potentially be holding a reference to the
+    // node.
+    if (res) {
+      // Spin until the node is no longer in use.
+      while (node->visitor.load() != 0)
+        LIBC_NAMESPACE::sleep_briefly();
+      delete node;
+    }
+    return res;
+  }
+};
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif

>From 0eac3c6dde7115a8c1d760f2892b963b3eebd3ef Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yifanzhu at rochester.edu>
Date: Wed, 30 Jul 2025 12:52:35 -0400
Subject: [PATCH 2/3] remove some extra braces

---
 libc/src/__support/aba_ptr.h    | 10 ++++------
 libc/src/__support/mpmc_stack.h |  3 +--
 2 files changed, 5 insertions(+), 8 deletions(-)

diff --git a/libc/src/__support/aba_ptr.h b/libc/src/__support/aba_ptr.h
index c702aae017502..f7ed601dfbe70 100644
--- a/libc/src/__support/aba_ptr.h
+++ b/libc/src/__support/aba_ptr.h
@@ -45,17 +45,15 @@ template <class T, bool IsAtomic> struct AbaPtrImpl {
         // Wrapping add for unsigned integers.
         next.tag = snapshot.tag + 1;
         if (__atomic_compare_exchange(&impl.atomic, &snapshot, &next, true,
-                                      __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
+                                      __ATOMIC_ACQ_REL, __ATOMIC_RELAXED))
           return;
-        }
       }
     } else {
       // Acquire the lock.
-      while (__atomic_exchange_n(&impl.mutex.locked, true, __ATOMIC_ACQUIRE)) {
-        while (__atomic_load_n(&impl.mutex.locked, __ATOMIC_RELAXED)) {
+      while (__atomic_exchange_n(&impl.mutex.locked, true, __ATOMIC_ACQUIRE))
+        while (__atomic_load_n(&impl.mutex.locked, __ATOMIC_RELAXED))
           LIBC_NAMESPACE::sleep_briefly();
-        }
-      }
+
       impl.mutex.ptr = op(impl.mutex.ptr);
       // Release the lock.
       __atomic_store_n(&impl.mutex.locked, false, __ATOMIC_RELEASE);
diff --git a/libc/src/__support/mpmc_stack.h b/libc/src/__support/mpmc_stack.h
index 4892c3926b4b0..819433f24380c 100644
--- a/libc/src/__support/mpmc_stack.h
+++ b/libc/src/__support/mpmc_stack.h
@@ -32,9 +32,8 @@ template <class T> class MPMCStack {
   LIBC_INLINE bool push(T value) {
     AllocChecker ac;
     Node *new_node = new Node(value, ac);
-    if (!ac) {
+    if (!ac)
       return false;
-    }
     head.transaction([new_node](Node *old_head) {
       new_node->next = old_head;
       return new_node;

>From 1ad958ea32c7515452e631ae751261632bf2ad6b Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Wed, 30 Jul 2025 16:21:43 -0400
Subject: [PATCH 3/3] add tests

---
 libc/src/__support/CMakeLists.txt             | 24 ++++++++
 libc/src/__support/aba_ptr.h                  | 17 +++++-
 libc/src/__support/mpmc_stack.h               | 15 ++---
 libc/test/IntegrationTest/test.cpp            |  9 +--
 .../integration/src/__support/CMakeLists.txt  | 15 +++++
 .../src/__support/mpmc_stack_test.cpp         | 61 +++++++++++++++++++
 lld/test/wasm/Inputs/libstub.so               | 12 ++--
 .../MC/AsmParser/preserve-comments-crlf.s     | 26 ++++----
 .../llvm-mca/X86/directives-handle-crlf.s     |  8 +--
 .../tools/split-file/Inputs/basic-aa.crlf     |  4 +-
 .../tools/split-file/Inputs/basic-bb.crlf     |  8 +--
 llvm/test/tools/split-file/basic.crlf.test    | 20 +++---
 12 files changed, 168 insertions(+), 51 deletions(-)
 create mode 100644 libc/test/integration/src/__support/mpmc_stack_test.cpp

diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index 2196d9e23bba7..c9d89cf6fc286 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -398,6 +398,30 @@ add_header_library(
     libc.src.__support.macros.attributes
 )
 
+add_header_library(
+  aba_ptr
+  HDRS
+    aba_ptr.h
+  DEPENDS
+    libc.hdr.types.size_t
+    libc.src.__support.common
+    libc.src.__support.threads.sleep
+)
+
+add_header_library(
+  mpmc_stack
+  HDRS
+    mpmc_stack.h
+  DEPENDS
+    libc.src.__support.aba_ptr
+    libc.src.__support.common
+    libc.src.__support.CPP.atomic
+    libc.src.__support.CPP.new
+    libc.src.__support.CPP.optional
+    libc.src.__support.CPP.type_traits
+)
+
+
 add_subdirectory(FPUtil)
 add_subdirectory(OSUtil)
 add_subdirectory(StringUtil)
diff --git a/libc/src/__support/aba_ptr.h b/libc/src/__support/aba_ptr.h
index f7ed601dfbe70..632cc466c295b 100644
--- a/libc/src/__support/aba_ptr.h
+++ b/libc/src/__support/aba_ptr.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_TAGGED_POINTER_H
 #define LLVM_LIBC_SRC___SUPPORT_TAGGED_POINTER_H
 
+#include "hdr/types/size_t.h"
 #include "src/__support/common.h"
 #include "src/__support/threads/sleep.h"
 
@@ -24,7 +25,7 @@ template <class T, bool IsAtomic> struct AbaPtrImpl {
   union Impl {
     struct alignas(2 * alignof(void *)) Atomic {
       T *ptr;
-      __SIZE_TYPE__ tag;
+      size_t tag;
     } atomic;
     struct Mutex {
       T *ptr;
@@ -59,6 +60,20 @@ template <class T, bool IsAtomic> struct AbaPtrImpl {
       __atomic_store_n(&impl.mutex.locked, false, __ATOMIC_RELEASE);
     }
   }
+
+  LIBC_INLINE T *get() const {
+    if constexpr (IsAtomic) {
+      // Weak micro-architectures typically reguards simultaneous partial word
+      // loading and full word loading as a race condition. While there are
+      // implementations that uses racy read anyway, we still load the whole
+      // word to avoid any complications.
+      typename Impl::Atomic snapshot;
+      __atomic_load(&impl.atomic, &snapshot, __ATOMIC_RELAXED);
+      return snapshot.ptr;
+    } else {
+      return impl.mutex.ptr;
+    }
+  }
 };
 
 template <class T> using AbaPtr = AbaPtrImpl<T, LIBC_ABA_PTR_IS_ATOMIC>;
diff --git a/libc/src/__support/mpmc_stack.h b/libc/src/__support/mpmc_stack.h
index 819433f24380c..c6546bac9be9a 100644
--- a/libc/src/__support/mpmc_stack.h
+++ b/libc/src/__support/mpmc_stack.h
@@ -12,13 +12,14 @@
 #include "src/__support/CPP/atomic.h"
 #include "src/__support/CPP/new.h"
 #include "src/__support/CPP/optional.h"
+#include "src/__support/CPP/type_traits.h"
 #include "src/__support/aba_ptr.h"
 
 namespace LIBC_NAMESPACE_DECL {
 template <class T> class MPMCStack {
   struct Node {
     cpp::Atomic<size_t> visitor;
-    AbaPtr<Node> next;
+    Node *next;
     T value;
 
     LIBC_INLINE Node(T val) : visitor(0), next(nullptr), value(val) {}
@@ -31,7 +32,7 @@ template <class T> class MPMCStack {
   LIBC_INLINE MPMCStack() : head(nullptr) {}
   LIBC_INLINE bool push(T value) {
     AllocChecker ac;
-    Node *new_node = new Node(value, ac);
+    Node *new_node = new (ac) Node(value);
     if (!ac)
       return false;
     head.transaction([new_node](Node *old_head) {
@@ -41,17 +42,17 @@ template <class T> class MPMCStack {
     return true;
   }
   LIBC_INLINE cpp::optional<T> pop() {
-    cpp::optional<T> res;
-    Node *node;
-    head.transaction([&](Node *current_head) {
+    cpp::optional<T> res = cpp::nullopt;
+    Node *node = nullptr;
+    head.transaction([&](Node *current_head) -> Node * {
       if (!current_head) {
         res = cpp::nullopt;
         return nullptr;
       }
       node = current_head;
       node->visitor.fetch_add(1);
-      res = node->value;
-      auto next = node->next;
+      res = cpp::optional<T>{node->value};
+      Node *next = node->next;
       node->visitor.fetch_sub(1);
       return next;
     });
diff --git a/libc/test/IntegrationTest/test.cpp b/libc/test/IntegrationTest/test.cpp
index 8baf74637b309..35ca312939ab3 100644
--- a/libc/test/IntegrationTest/test.cpp
+++ b/libc/test/IntegrationTest/test.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/stdint_proxy.h"
+#include "src/__support/CPP/atomic.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 #include <stddef.h>
@@ -65,14 +66,14 @@ int atexit(void (*func)(void)) { return LIBC_NAMESPACE::atexit(func); }
 
 static constexpr uint64_t MEMORY_SIZE = 16384;
 static uint8_t memory[MEMORY_SIZE];
-static uint8_t *ptr = memory;
+static LIBC_NAMESPACE::cpp::Atomic<size_t> cursor = 0;
 
 extern "C" {
 
 void *malloc(size_t s) {
-  void *mem = ptr;
-  ptr += s;
-  return static_cast<uint64_t>(ptr - memory) >= MEMORY_SIZE ? nullptr : mem;
+  size_t cur = cursor.fetch_add(s);
+  void *mem = &memory[cur];
+  return cur + s <= MEMORY_SIZE ? mem : nullptr;
 }
 
 void free(void *) {}
diff --git a/libc/test/integration/src/__support/CMakeLists.txt b/libc/test/integration/src/__support/CMakeLists.txt
index b5b6557e8d689..93f54083f3c00 100644
--- a/libc/test/integration/src/__support/CMakeLists.txt
+++ b/libc/test/integration/src/__support/CMakeLists.txt
@@ -2,3 +2,18 @@ add_subdirectory(threads)
 if(LIBC_TARGET_OS_IS_GPU)
   add_subdirectory(GPU)
 endif()
+
+add_libc_integration_test_suite(libc-support-integration-tests)
+
+add_integration_test(
+  mpmc_stack_test
+  SUITE
+    libc-support-integration-tests
+  SRCS
+    mpmc_stack_test.cpp
+  DEPENDS
+    libc.src.__support.mpmc_stack
+    libc.src.__support.threads.thread
+    libc.src.pthread.pthread_create
+    libc.src.pthread.pthread_join
+)
diff --git a/libc/test/integration/src/__support/mpmc_stack_test.cpp b/libc/test/integration/src/__support/mpmc_stack_test.cpp
new file mode 100644
index 0000000000000..3cc8237c9e179
--- /dev/null
+++ b/libc/test/integration/src/__support/mpmc_stack_test.cpp
@@ -0,0 +1,61 @@
+#include "src/__support/CPP/atomic.h"
+#include "src/__support/mpmc_stack.h"
+#include "src/pthread/pthread_create.h"
+#include "src/pthread/pthread_join.h"
+#include "test/IntegrationTest/test.h"
+
+using namespace LIBC_NAMESPACE;
+
+void smoke_test() {
+  MPMCStack<int> stack;
+  for (int i = 0; i <= 100; ++i)
+    if (!stack.push(i))
+      __builtin_trap();
+  for (int i = 100; i >= 0; --i)
+    if (*stack.pop() != i)
+      __builtin_trap();
+  if (stack.pop())
+    __builtin_trap(); // Should be empty now.
+}
+
+void multithread_test() {
+  constexpr static size_t NUM_THREADS = 5;
+  constexpr static size_t NUM_PUSHES = 100;
+  struct State {
+    MPMCStack<size_t> stack;
+    cpp::Atomic<size_t> counter = 0;
+    cpp::Atomic<bool> flags[NUM_PUSHES];
+  } state;
+  pthread_t threads[NUM_THREADS];
+  for (size_t i = 0; i < NUM_THREADS; ++i) {
+    LIBC_NAMESPACE::pthread_create(
+        &threads[i], nullptr,
+        [](void *arg) -> void * {
+          State *state = static_cast<State *>(arg);
+          for (;;) {
+            size_t current = state->counter.fetch_add(1);
+            if (current >= NUM_PUSHES)
+              break;
+            if (!state->stack.push(current))
+              __builtin_trap();
+          }
+          while (auto res = state->stack.pop())
+            state->flags[res.value()].store(true);
+          return nullptr;
+        },
+        &state);
+  }
+  for (pthread_t thread : threads)
+    LIBC_NAMESPACE::pthread_join(thread, nullptr);
+  while (cpp::optional<size_t> res = state.stack.pop())
+    state.flags[res.value()].store(true);
+  for (size_t i = 0; i < NUM_PUSHES; ++i)
+    if (!state.flags[i].load())
+      __builtin_trap();
+}
+
+TEST_MAIN() {
+  smoke_test();
+  multithread_test();
+  return 0;
+}
diff --git a/lld/test/wasm/Inputs/libstub.so b/lld/test/wasm/Inputs/libstub.so
index 70f6648c60eee..99783d72f26bd 100644
--- a/lld/test/wasm/Inputs/libstub.so
+++ b/lld/test/wasm/Inputs/libstub.so
@@ -1,6 +1,6 @@
-#STUB
-# This is a comment
-foo_import: foodep1,foodep2
-# This symbols as no dependencies
-bar
-baz: bazdep
+#STUB
+# This is a comment
+foo_import: foodep1,foodep2
+# This symbols as no dependencies
+bar
+baz: bazdep
diff --git a/llvm/test/MC/AsmParser/preserve-comments-crlf.s b/llvm/test/MC/AsmParser/preserve-comments-crlf.s
index 27c5b2e070719..8bf094b73ef96 100644
--- a/llvm/test/MC/AsmParser/preserve-comments-crlf.s
+++ b/llvm/test/MC/AsmParser/preserve-comments-crlf.s
@@ -1,13 +1,13 @@
-	#RUN: llvm-mc -preserve-comments -n -triple i386-linux-gnu < %s > %t
-	#RUN: diff -b %s %t
-	.text
-
-foo:	#Comment here
-	#comment here
-	nop
-	#if DIRECTIVE COMMENT
-	## WHOLE LINE COMMENT
-	cmpl	$196, %eax	## EOL COMMENT
-	#endif
-	.ident	"clang version 3.9.0"
-	.section	".note.GNU-stack","", at progbits
+	#RUN: llvm-mc -preserve-comments -n -triple i386-linux-gnu < %s > %t
+	#RUN: diff -b %s %t
+	.text
+
+foo:	#Comment here
+	#comment here
+	nop
+	#if DIRECTIVE COMMENT
+	## WHOLE LINE COMMENT
+	cmpl	$196, %eax	## EOL COMMENT
+	#endif
+	.ident	"clang version 3.9.0"
+	.section	".note.GNU-stack","", at progbits
diff --git a/llvm/test/tools/llvm-mca/X86/directives-handle-crlf.s b/llvm/test/tools/llvm-mca/X86/directives-handle-crlf.s
index db797a75c2617..67821474be72a 100644
--- a/llvm/test/tools/llvm-mca/X86/directives-handle-crlf.s
+++ b/llvm/test/tools/llvm-mca/X86/directives-handle-crlf.s
@@ -1,4 +1,4 @@
-# RUN: llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=generic %s
-# LLVM-MCA-BEGIN foo
-addl	$42, %eax
-# LLVM-MCA-END
+# RUN: llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=generic %s
+# LLVM-MCA-BEGIN foo
+addl	$42, %eax
+# LLVM-MCA-END
diff --git a/llvm/test/tools/split-file/Inputs/basic-aa.crlf b/llvm/test/tools/split-file/Inputs/basic-aa.crlf
index 0b9ddeb2fc12a..aa8822c8e1453 100644
--- a/llvm/test/tools/split-file/Inputs/basic-aa.crlf
+++ b/llvm/test/tools/split-file/Inputs/basic-aa.crlf
@@ -1,2 +1,2 @@
-
-aa
+
+aa
diff --git a/llvm/test/tools/split-file/Inputs/basic-bb.crlf b/llvm/test/tools/split-file/Inputs/basic-bb.crlf
index b6c3c808ec62f..3c765c42c0127 100644
--- a/llvm/test/tools/split-file/Inputs/basic-bb.crlf
+++ b/llvm/test/tools/split-file/Inputs/basic-bb.crlf
@@ -1,4 +1,4 @@
-
-
-
-bb
+
+
+
+bb
diff --git a/llvm/test/tools/split-file/basic.crlf.test b/llvm/test/tools/split-file/basic.crlf.test
index f01074a879630..6d89c69064e5e 100644
--- a/llvm/test/tools/split-file/basic.crlf.test
+++ b/llvm/test/tools/split-file/basic.crlf.test
@@ -1,10 +1,10 @@
-#--- aa
-aa
-;--- bb
-bb
-;--- end
-
-# RUN: rm -rf %t
-# RUN: split-file --leading-lines %s %t
-# RUN: diff %S/Inputs/basic-aa.crlf %t/aa
-# RUN: diff %S/Inputs/basic-bb.crlf %t/bb
+#--- aa
+aa
+;--- bb
+bb
+;--- end
+
+# RUN: rm -rf %t
+# RUN: split-file --leading-lines %s %t
+# RUN: diff %S/Inputs/basic-aa.crlf %t/aa
+# RUN: diff %S/Inputs/basic-bb.crlf %t/bb



More information about the llvm-commits mailing list